╔══════════════════════════════════════════════════════════════════════════╗ ║ 📚 HARVESTER LOGIC EXPLAINED - GUIDE COMPLET 📚 ║ ╚══════════════════════════════════════════════════════════════════════════╝ 📅 Date: 3 Octobre 2025 🎮 Game: RTS Web - Red Alert Style 🚜 Subject: Harvester, Refinery & Resource System ══════════════════════════════════════════════════════════════════════════ 🎯 PROBLÈME RAPPORTÉ "Harvester didn't go collect resource" Le Harvester ne collecte pas automatiquement les ressources. ══════════════════════════════════════════════════════════════════════════ 📋 SYSTÈME DE RESSOURCES - VUE D'ENSEMBLE ┌────────────────────────────────────────────────────────────────────────┐ │ CYCLE COMPLET DU HARVESTER │ └────────────────────────────────────────────────────────────────────────┘ 1. SPAWN (Création) └─> Harvester produit depuis HQ (pas Refinery!) Coût: 200 crédits 2. IDLE (Repos) └─> Cherche automatiquement le minerai le plus proche Variables: gathering=False, ore_target=None 3. SEARCHING (Recherche) └─> find_nearest_ore() trouve ORE ou GEM Variables: gathering=True, ore_target=Position(x, y) 4. MOVING TO ORE (Déplacement vers minerai) └─> Se déplace vers ore_target Vitesse: 1.0 tiles/tick 5. HARVESTING (Récolte) └─> À proximité du minerai (< TILE_SIZE/2) ORE: +50 crédits par tile GEM: +100 crédits par tile Tile devient GRASS après récolte 6. CARGO CHECK (Vérification cargaison) └─> Si cargo >= 180 (90% de 200) → Variables: returning=True 7. RETURNING (Retour base) └─> find_nearest_depot() trouve Refinery ou HQ Se déplace vers le dépôt 8. DEPOSITING (Dépôt) └─> À proximité du dépôt (< TILE_SIZE*2) player.credits += unit.cargo cargo = 0 Variables: returning=False, gathering=False 9. REPEAT (Recommence) └─> Retour à l'étape 2 (IDLE) ══════════════════════════════════════════════════════════════════════════ 🔧 CONSTANTES SYSTÈME HARVESTER_CAPACITY = 200 # Capacité max cargo HARVEST_AMOUNT_PER_ORE = 50 # Crédits par tile ORE HARVEST_AMOUNT_PER_GEM = 100 # Crédits par tile GEM TILE_SIZE = 40 # Taille d'une tile en pixels MAP_WIDTH = 96 # Largeur carte en tiles MAP_HEIGHT = 72 # Hauteur carte en tiles Production: ├─ Coût: 200 crédits ├─ Bâtiment requis: HQ (pas Refinery!) ├─ Santé: 150 HP ├─ Vitesse: 1.0 ├─ Dégâts: 0 (sans arme) └─ Portée: 0 ══════════════════════════════════════════════════════════════════════════ 🏗️ BÂTIMENTS IMPLIQUÉS ┌────────────────────────────────────────────────────────────────────────┐ │ 1. HQ (Headquarters / Quartier Général) │ ├────────────────────────────────────────────────────────────────────────┤ │ Rôle: │ │ • PRODUIT les Harvesters (pas le Refinery!) │ │ • Sert de DÉPÔT pour déposer les ressources │ │ • Présent au démarrage du jeu │ │ │ │ Code: │ │ PRODUCTION_REQUIREMENTS = { │ │ UnitType.HARVESTER: BuildingType.HQ # ← Important! │ │ } │ │ │ │ Fonction dépôt: │ │ find_nearest_depot() retourne HQ OU Refinery │ └────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────┐ │ 2. REFINERY (Raffinerie) │ ├────────────────────────────────────────────────────────────────────────┤ │ Rôle: │ │ • NE PRODUIT PAS de Harvesters │ │ • Sert de DÉPÔT pour déposer les ressources │ │ • Optionnel (HQ suffit) │ │ • Coût construction: 600 crédits │ │ │ │ Avantages: │ │ • Peut être construit près des champs de minerai │ │ • Réduit le temps de trajet des Harvesters │ │ • Optimise l'économie │ │ │ │ Fonction dépôt: │ │ find_nearest_depot() retourne HQ OU Refinery │ └────────────────────────────────────────────────────────────────────────┘ ══════════════════════════════════════════════════════════════════════════ 🌍 TYPES DE TERRAIN TerrainType.GRASS 🟩 Herbe (normal, traversable) TerrainType.ORE 🟨 Minerai (50 crédits, devient GRASS après) TerrainType.GEM 💎 Gemme (100 crédits, devient GRASS après) TerrainType.WATER 🟦 Eau (obstacle, non traversable) Génération carte (init_map): ├─ 15 patches d'ORE (5x5 tiles chacun, ~70% densité) ├─ 5 patches de GEM (3x3 tiles chacun, ~50% densité) └─ 8 corps d'eau (7x7 tiles chacun) ══════════════════════════════════════════════════════════════════════════ 🔄 LOGIQUE HARVESTER - CODE DÉTAILLÉ ┌────────────────────────────────────────────────────────────────────────┐ │ update_harvester(unit: Unit) │ │ Appelé chaque tick (50ms) pour chaque Harvester │ └────────────────────────────────────────────────────────────────────────┘ def update_harvester(self, unit: Unit): """RED ALERT: Harvester AI - auto-collect resources!""" # ═══════════════════════════════════════════════════════════════ # ÉTAT 1: RETURNING (Retour au dépôt avec cargaison) # ═══════════════════════════════════════════════════════════════ if unit.returning: # Trouver le dépôt le plus proche (Refinery ou HQ) depot = self.find_nearest_depot(unit.player_id, unit.position) if depot: distance = unit.position.distance_to(depot.position) # Arrivé au dépôt? if distance < TILE_SIZE * 2: # < 80 pixels # ✅ DÉPOSER LA CARGAISON self.game_state.players[unit.player_id].credits += unit.cargo unit.cargo = 0 unit.returning = False unit.gathering = False unit.ore_target = None # → Retour à l'état IDLE (cherchera du minerai) else: # 🚶 SE DÉPLACER VERS LE DÉPÔT unit.target = Position(depot.position.x, depot.position.y) else: # ❌ PAS DE DÉPÔT (HQ détruit?) unit.returning = False return # ← Sortie de la fonction # ═══════════════════════════════════════════════════════════════ # ÉTAT 2: AT ORE PATCH (Au minerai, en train de récolter) # ═══════════════════════════════════════════════════════════════ if unit.ore_target: distance = ((unit.position.x - unit.ore_target.x) ** 2 + (unit.position.y - unit.ore_target.y) ** 2) ** 0.5 # Arrivé au minerai? if distance < TILE_SIZE / 2: # < 20 pixels # Convertir position en coordonnées de tile tile_x = int(unit.ore_target.x / TILE_SIZE) tile_y = int(unit.ore_target.y / TILE_SIZE) # Vérifier limites carte if (0 <= tile_x < MAP_WIDTH and 0 <= tile_y < MAP_HEIGHT): terrain = self.game_state.terrain[tile_y][tile_x] # ⛏️ RÉCOLTER LE MINERAI if terrain == TerrainType.ORE: unit.cargo = min(HARVESTER_CAPACITY, unit.cargo + HARVEST_AMOUNT_PER_ORE) # Tile devient herbe self.game_state.terrain[tile_y][tile_x] = TerrainType.GRASS elif terrain == TerrainType.GEM: unit.cargo = min(HARVESTER_CAPACITY, unit.cargo + HARVEST_AMOUNT_PER_GEM) # Tile devient herbe self.game_state.terrain[tile_y][tile_x] = TerrainType.GRASS # Réinitialiser état unit.ore_target = None unit.gathering = False # 📦 CARGAISON PLEINE? if unit.cargo >= HARVESTER_CAPACITY * 0.9: # ≥ 180 unit.returning = True # → Retour au dépôt unit.target = None else: # 🚶 SE DÉPLACER VERS LE MINERAI unit.target = unit.ore_target return # ← Sortie de la fonction # ═══════════════════════════════════════════════════════════════ # ÉTAT 3: IDLE (Chercher du minerai) # ═══════════════════════════════════════════════════════════════ if not unit.gathering and not unit.target: # 🔍 CHERCHER LE MINERAI LE PLUS PROCHE nearest_ore = self.find_nearest_ore(unit.position) if nearest_ore: unit.ore_target = nearest_ore unit.gathering = True unit.target = nearest_ore # → Passera à l'état AT ORE PATCH au prochain tick ══════════════════════════════════════════════════════════════════════════ 🔍 FONCTIONS AUXILIAIRES ┌────────────────────────────────────────────────────────────────────────┐ │ find_nearest_depot(player_id, position) → Building | None │ ├────────────────────────────────────────────────────────────────────────┤ │ Trouve le dépôt le plus proche (Refinery OU HQ) du joueur │ │ │ │ Logique: │ │ 1. Parcourt tous les bâtiments │ │ 2. Filtre par player_id │ │ 3. Filtre par type: REFINERY ou HQ │ │ 4. Calcule distance euclidienne │ │ 5. Retourne le plus proche │ │ │ │ Retour: │ │ • Building si trouvé │ │ • None si aucun dépôt (HQ détruit et pas de Refinery) │ └────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────┐ │ find_nearest_ore(position) → Position | None │ ├────────────────────────────────────────────────────────────────────────┤ │ Trouve le minerai (ORE ou GEM) le plus proche │ │ │ │ Logique: │ │ 1. Parcourt toutes les tiles de la carte (96×72 = 6,912 tiles) │ │ 2. Filtre par terrain: ORE ou GEM │ │ 3. Calcule position centre tile: (x*40+20, y*40+20) │ │ 4. Calcule distance euclidienne │ │ 5. Retourne position du plus proche │ │ │ │ Retour: │ │ • Position(x, y) si minerai trouvé │ │ • None si tout le minerai épuisé │ │ │ │ Performance: │ │ • O(n²) où n = taille carte │ │ • Appelé une fois quand Harvester devient idle │ │ • Pas de cache (minerai change dynamiquement) │ └────────────────────────────────────────────────────────────────────────┘ ══════════════════════════════════════════════════════════════════════════ 🐛 PROBLÈMES POSSIBLES & SOLUTIONS ┌────────────────────────────────────────────────────────────────────────┐ │ PROBLÈME 1: Harvester ne bouge pas du tout │ ├────────────────────────────────────────────────────────────────────────┤ │ Causes possibles: │ │ ❌ Harvester pas produit depuis HQ │ │ ❌ update_harvester() pas appelé dans game loop │ │ ❌ Tout le minerai déjà épuisé │ │ ❌ Variables Unit pas initialisées (gathering, returning, etc.) │ │ │ │ Solutions: │ │ ✅ Vérifier: UnitType.HARVESTER: BuildingType.HQ │ │ ✅ Vérifier: if unit.type == UnitType.HARVESTER: │ │ ✅ Vérifier terrain: print(self.game_state.terrain) │ │ ✅ Vérifier init Unit: cargo=0, gathering=False, returning=False │ └────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────┐ │ PROBLÈME 2: Harvester va au minerai mais ne récolte pas │ ├────────────────────────────────────────────────────────────────────────┤ │ Causes possibles: │ │ ❌ Distance check trop strict (< TILE_SIZE/2) │ │ ❌ Coordonnées tile mal calculées (int conversion) │ │ ❌ Terrain déjà GRASS (autre Harvester a pris) │ │ ❌ Limites carte mal vérifiées │ │ │ │ Solutions: │ │ ✅ Augmenter distance: if distance < TILE_SIZE │ │ ✅ Debug: print(f"At ore: {tile_x},{tile_y} = {terrain}") │ │ ✅ Vérifier race condition entre Harvesters │ │ ✅ Vérifier: 0 <= tile_x < MAP_WIDTH │ └────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────┐ │ PROBLÈME 3: Harvester ne retourne pas au dépôt │ ├────────────────────────────────────────────────────────────────────────┤ │ Causes possibles: │ │ ❌ Pas de Refinery ni HQ (détruit?) │ │ ❌ Cargo pas incrémenté (reste à 0) │ │ ❌ Check cargo >= 180 jamais atteint │ │ ❌ Variable returning pas setée │ │ │ │ Solutions: │ │ ✅ Vérifier HQ existe: buildings[id].type == BuildingType.HQ │ │ ✅ Debug: print(f"Cargo: {unit.cargo}/200") │ │ ✅ Test manuel: unit.returning = True │ │ ✅ Construire Refinery près du minerai │ └────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────┐ │ PROBLÈME 4: Harvester au dépôt mais ne dépose pas │ ├────────────────────────────────────────────────────────────────────────┤ │ Causes possibles: │ │ ❌ Distance check trop strict (< TILE_SIZE*2) │ │ ❌ Position dépôt mal calculée │ │ ❌ Credits pas incrémentés côté player │ │ ❌ Variables pas réinitialisées après dépôt │ │ │ │ Solutions: │ │ ✅ Augmenter distance: if distance < TILE_SIZE*3 │ │ ✅ Debug: print(f"At depot: distance={distance}") │ │ ✅ Vérifier: player.credits += unit.cargo │ │ ✅ Vérifier: cargo=0, returning=False après dépôt │ └────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────┐ │ PROBLÈME 5: Frontend ne montre pas le Harvester │ ├────────────────────────────────────────────────────────────────────────┤ │ Causes possibles: │ │ ❌ WebSocket state pas broadcast │ │ ❌ game.js ne render pas type "harvester" │ │ ❌ Harvester hors viewport (carte 96×72) │ │ ❌ Cache côté client │ │ │ │ Solutions: │ │ ✅ Vérifier broadcast: state_dict['units'] │ │ ✅ Vérifier game.js: case 'harvester': ... │ │ ✅ Tester via curl /health (units count) │ │ ✅ F5 refresh browser │ └────────────────────────────────────────────────────────────────────────┘ ══════════════════════════════════════════════════════════════════════════ 🧪 TESTS & DEBUGGING ┌────────────────────────────────────────────────────────────────────────┐ │ TEST 1: Vérifier création Harvester │ ├────────────────────────────────────────────────────────────────────────┤ │ curl http://localhost:7860/health │ │ │ │ Résultat attendu: │ │ { │ │ "status": "healthy", │ │ "units": 8, ← Devrait augmenter après production Harvester │ │ ... │ │ } │ └────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────┐ │ TEST 2: Vérifier minerai sur la carte │ ├────────────────────────────────────────────────────────────────────────┤ │ Ajouter dans app.py: │ │ │ │ ore_count = sum(1 for row in self.terrain │ │ for tile in row │ │ if tile in [TerrainType.ORE, TerrainType.GEM]) │ │ print(f"Ore tiles remaining: {ore_count}") │ └────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────┐ │ TEST 3: Debug état Harvester │ ├────────────────────────────────────────────────────────────────────────┤ │ Ajouter dans update_harvester(): │ │ │ │ print(f"Harvester {unit.id[:8]}:") │ │ print(f" Position: ({unit.position.x:.0f}, {unit.position.y:.0f}")│ │ print(f" Cargo: {unit.cargo}/200") │ │ print(f" Gathering: {unit.gathering}") │ │ print(f" Returning: {unit.returning}") │ │ print(f" Ore target: {unit.ore_target}") │ └────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────┐ │ TEST 4: Vérifier dépôts disponibles │ ├────────────────────────────────────────────────────────────────────────┤ │ Ajouter dans find_nearest_depot(): │ │ │ │ depots = [b for b in self.game_state.buildings.values() │ │ if b.player_id == player_id │ │ and b.type in [BuildingType.REFINERY, BuildingType.HQ]] │ │ print(f"Available depots for player {player_id}: {len(depots)}") │ └────────────────────────────────────────────────────────────────────────┘ ══════════════════════════════════════════════════════════════════════════ ✅ CHECKLIST FONCTIONNEMENT HARVESTER Pour que le Harvester fonctionne correctement, vérifier: □ 1. PRODUCTION ✓ HQ existe et appartient au joueur ✓ PRODUCTION_REQUIREMENTS: HARVESTER → HQ ✓ Player a ≥200 crédits ✓ Commande WebSocket "build_unit" avec type="harvester" □ 2. CRÉATION UNITÉ ✓ create_unit(UnitType.HARVESTER, player_id, position) ✓ Unit.cargo = 0 ✓ Unit.gathering = False ✓ Unit.returning = False ✓ Unit.ore_target = None □ 3. GAME LOOP ✓ update_game_state() appelle update_harvester(unit) ✓ Tick rate: 50ms (20 ticks/sec) ✓ Broadcast state inclut units □ 4. TERRAIN ✓ init_map() génère ORE et GEM ✓ ≥15 patches d'ORE sur la carte ✓ Tiles accessibles (pas entourées d'eau) □ 5. MOUVEMENT ✓ unit.target setté correctement ✓ unit.speed = 1.0 ✓ Position mise à jour chaque tick ✓ Distance calculée correctement □ 6. RÉCOLTE ✓ Distance < TILE_SIZE/2 pour récolter ✓ tile_x, tile_y calculés correctement ✓ Terrain type vérifié (ORE ou GEM) ✓ Cargo incrémenté ✓ Tile devient GRASS □ 7. RETOUR DÉPÔT ✓ cargo >= 180 → returning = True ✓ find_nearest_depot() trouve HQ ou Refinery ✓ Distance < TILE_SIZE*2 pour déposer ✓ player.credits += cargo ✓ cargo reset à 0 □ 8. FRONTEND ✓ WebSocket connecté ✓ game.js render type "harvester" ✓ Couleur/sprite défini ✓ Broadcast state reçu ══════════════════════════════════════════════════════════════════════════ 📊 MÉTRIQUES PERFORMANCE Harvester optimal (temps pour 1 cycle): ├─ Recherche minerai: ~1 sec (instant si proche) ├─ Trajet vers minerai: Variable (dépend distance) ├─ Récolte 4 tiles: ~4 ticks = 0.2 sec ├─ Trajet vers dépôt: Variable (dépend distance) ├─ Dépôt: ~1 tick = 0.05 sec └─ Total: ~10-30 sec par cycle Revenus par Harvester: ├─ Cargo max: 200 crédits ├─ ORE: 4 tiles = 200 crédits ├─ GEM: 2 tiles = 200 crédits └─ Cycles/min: 2-6 (selon distance) ROI (Return on Investment): ├─ Coût: 200 crédits ├─ Premier cycle: Break-even ├─ Profit net: Tous cycles suivants └─ Recommandé: 2-3 Harvesters minimum ══════════════════════════════════════════════════════════════════════════ 📖 EXEMPLE SCÉNARIO COMPLET Tick 0: Player construit HQ Tick 100: Player a 5000 crédits Tick 101: Player envoie commande: build_unit("harvester") Tick 102: Check: HQ existe? ✅ Credits ≥200? ✅ Tick 103: Credits: 5000 - 200 = 4800 Tick 104: Harvester créé à position (HQ.x+80, HQ.y+80) Tick 105: update_harvester() appelé État: IDLE (gathering=False, ore_target=None) Tick 106: find_nearest_ore() cherche minerai → Trouve ORE à (1200, 800) État: ore_target=(1200,800), gathering=True Tick 107-200: Se déplace vers (1200, 800) Vitesse: 1.0 pixel/tick Tick 201: Arrivé au minerai (distance < 20) tile_x=30, tile_y=20 terrain[20][30] = ORE Tick 202: Récolte: cargo += 50 → cargo=50 terrain[20][30] = GRASS État: ore_target=None, gathering=False Tick 203: Cherche nouveau minerai proche → Trouve ORE adjacent à (1240, 800) Tick 204-220: Se déplace et récolte 3 autres tiles ORE cargo: 50 → 100 → 150 → 200 Tick 221: cargo=200 ≥ 180 → returning=True Tick 222: find_nearest_depot() → Trouve HQ à (200, 200) Tick 223-350: Se déplace vers HQ Tick 351: Arrivé au HQ (distance < 80) Tick 352: Dépôt: player.credits += 200 → 5000 cargo=0, returning=False Tick 353: État: IDLE → Cherche nouveau minerai Tick 354: Cycle recommence... ══════════════════════════════════════════════════════════════════════════ Date: 3 Octobre 2025 Status: ✅ DOCUMENTATION COMPLÈTE "The Harvester must flow." 🚜💰