Spaces:
Runtime error
Runtime error
| """ | |
| BotPlayer — simple rule-based AI opponent. | |
| Strategy phases (evaluated every BOT_TICK_INTERVAL ticks ≈ 2 s): | |
| 1. SCVs gather minerals immediately | |
| 2. Build Supply Depot when minerals >= 100 | |
| 3. Build Barracks when Supply Depot active & minerals >= 150 | |
| 4. Train Marines continuously when Barracks active | |
| 5. Attack enemy base when military count >= 4 | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| from typing import TYPE_CHECKING | |
| from .buildings import BuildingStatus, BuildingType | |
| from .commands import ActionType, GameAction, ParsedCommand | |
| from .units import UnitType, UNIT_DEFS | |
| if TYPE_CHECKING: | |
| from .engine import GameEngine | |
| log = logging.getLogger(__name__) | |
| BOT_PLAYER_ID = "__bot__" | |
| BOT_PLAYER_NAME = "Bot IA" | |
| # Ticks between each bot decision (~4 ticks/s → 10 ticks ≈ 2.5 s) | |
| BOT_TICK_INTERVAL = 10 | |
| def _cmd(*actions: GameAction) -> ParsedCommand: | |
| return ParsedCommand(actions=list(actions), feedback_template="") | |
| class BotPlayer: | |
| """Rule-based bot that drives one player slot in the game engine.""" | |
| def __init__(self, engine: "GameEngine", player_id: str) -> None: | |
| self.engine = engine | |
| self.player_id = player_id | |
| self._initial_gather_done = False | |
| # ------------------------------------------------------------------ | |
| # Main entry point — called by engine every BOT_TICK_INTERVAL ticks | |
| # ------------------------------------------------------------------ | |
| def act(self) -> None: | |
| state = self.engine.state | |
| player = state.players.get(self.player_id) | |
| if not player: | |
| return | |
| # 1. Send all SCVs to gather minerals (once) | |
| if not self._initial_gather_done: | |
| self.engine.apply_command( | |
| self.player_id, | |
| _cmd(GameAction( | |
| type=ActionType.GATHER, | |
| unit_selector="all_scv", | |
| resource_type="minerals", | |
| )), | |
| ) | |
| self._initial_gather_done = True | |
| # 2. Supply Depot | |
| if ( | |
| player.minerals >= 100 | |
| and not player.has_active(BuildingType.SUPPLY_DEPOT) | |
| and not _building_in_progress(player, BuildingType.SUPPLY_DEPOT) | |
| ): | |
| self.engine.apply_command( | |
| self.player_id, | |
| _cmd(GameAction( | |
| type=ActionType.BUILD, | |
| building_type=BuildingType.SUPPLY_DEPOT.value, | |
| )), | |
| ) | |
| # 3. Barracks | |
| if ( | |
| player.minerals >= 150 | |
| and player.has_active(BuildingType.SUPPLY_DEPOT) | |
| and not player.has_active(BuildingType.BARRACKS) | |
| and not _building_in_progress(player, BuildingType.BARRACKS) | |
| ): | |
| self.engine.apply_command( | |
| self.player_id, | |
| _cmd(GameAction( | |
| type=ActionType.BUILD, | |
| building_type=BuildingType.BARRACKS.value, | |
| )), | |
| ) | |
| # 4. Train Marines (only if supply and minerals allow) | |
| marine_cost = UNIT_DEFS[UnitType.MARINE].mineral_cost | |
| marine_supply = UNIT_DEFS[UnitType.MARINE].supply_cost | |
| queued_supply = _queued_supply(player) | |
| free_supply = player.supply_max - player.supply_used - queued_supply | |
| can_train_count = max(0, min(2, free_supply // marine_supply, player.minerals // marine_cost)) | |
| if ( | |
| player.has_active(BuildingType.BARRACKS) | |
| and can_train_count >= 1 | |
| ): | |
| self.engine.apply_command( | |
| self.player_id, | |
| _cmd(GameAction( | |
| type=ActionType.TRAIN, | |
| unit_type=UnitType.MARINE.value, | |
| count=can_train_count, | |
| )), | |
| ) | |
| # 5. Attack with military when strong enough | |
| military = [u for u in player.units.values() if u.unit_type != UnitType.SCV] | |
| if len(military) >= 4: | |
| self.engine.apply_command( | |
| self.player_id, | |
| _cmd(GameAction( | |
| type=ActionType.ATTACK, | |
| unit_selector="all_military", | |
| target_zone="enemy_base", | |
| )), | |
| ) | |
| # ------------------------------------------------------------------ | |
| # Helpers | |
| # ------------------------------------------------------------------ | |
| def _building_in_progress(player, bt: BuildingType) -> bool: | |
| return any( | |
| b.building_type == bt and b.status == BuildingStatus.CONSTRUCTING | |
| for b in player.buildings.values() | |
| ) | |
| def _queued_supply(player) -> int: | |
| """Supply that will be used by units already in production queues.""" | |
| total = 0 | |
| for b in player.buildings.values(): | |
| if b.status in (BuildingStatus.CONSTRUCTING, BuildingStatus.DESTROYED): | |
| continue | |
| for item in b.production_queue: | |
| try: | |
| ut = UnitType(item.unit_type) | |
| total += UNIT_DEFS[ut].supply_cost | |
| except ValueError: | |
| pass | |
| return total | |