Spaces:
Running
Running
| from __future__ import annotations | |
| import random | |
| from dataclasses import dataclass | |
| from typing import Dict, List, Tuple | |
| import pygame | |
| from config import constants | |
| ItemType = str | |
| class ItemSpec: | |
| color: Tuple[int, int, int] | |
| label: str | |
| ITEM_SPECS: Dict[ItemType, ItemSpec] = { | |
| # Metals (asteroids) | |
| "steel_scrap": ItemSpec((160, 160, 160), "Stl"), | |
| "aluminum_sheet": ItemSpec((180, 200, 220), "Al"), | |
| "copper_wire": ItemSpec((200, 140, 90), "Cu"), | |
| # Enemy drops | |
| "lithium_battery": ItemSpec((140, 200, 255), "Li"), | |
| "circuit_board": ItemSpec((90, 200, 120), "PCB"), | |
| "rare_earth_element": ItemSpec((200, 160, 255), "REE"), | |
| "pet_plastic": ItemSpec((200, 200, 220), "PET"), | |
| "carbon_fiber": ItemSpec((80, 80, 90), "CF"), | |
| "titanium_alloy": ItemSpec((170, 170, 190), "Ti"), | |
| "power_core": ItemSpec((255, 100, 80), "Core"), | |
| "crystal_capacitor": ItemSpec((180, 220, 255), "Cap"), | |
| "exotic_isotope": ItemSpec((255, 180, 80), "Iso"), | |
| # Health | |
| "repair_nanites": ItemSpec((90, 240, 140), "Heal"), | |
| "bio_gel": ItemSpec((90, 200, 90), "Gel"), | |
| # Power-ups (enemy drops) | |
| "powerup_magnet": ItemSpec((90, 200, 255), "Mag"), | |
| "powerup_super_magnet": ItemSpec((110, 220, 255), "SMag"), | |
| "powerup_weapon_missile": ItemSpec((255, 190, 80), "MSL"), | |
| "powerup_weapon_plasma": ItemSpec((120, 200, 255), "PLS"), | |
| "powerup_weapon_ray": ItemSpec((255, 120, 200), "RAY"), | |
| "powerup_shieldgen": ItemSpec((140, 220, 255), "SHD"), | |
| "powerup_rapidfire": ItemSpec((255, 220, 90), "RF"), | |
| "powerup_damage": ItemSpec((255, 120, 120), "Dmg"), | |
| "powerup_shield": ItemSpec((120, 180, 255), "Shd"), | |
| "powerup_regen": ItemSpec((90, 240, 140), "Regen"), | |
| "powerup_slow": ItemSpec((120, 180, 255), "Slow"), | |
| "powerup_multishot": ItemSpec((255, 160, 255), "2x"), | |
| } | |
| def random_metal_drop() -> ItemType: | |
| r = random.random() | |
| cumulative = 0.0 | |
| table = constants.ASTEROID_METAL_DROP_TABLE | |
| for name, prob in table: | |
| cumulative += prob | |
| if r <= cumulative: | |
| return name | |
| return table[-1][0] | |
| def random_enemy_drop() -> ItemType: | |
| table = constants.ENEMY_DROP_TABLE | |
| total = sum(weight for _, weight in table) | |
| r = random.uniform(0, total) | |
| cumulative = 0.0 | |
| for name, weight in table: | |
| cumulative += weight | |
| if r <= cumulative: | |
| return name | |
| return table[-1][0] | |
| def random_powerup_drop() -> ItemType: | |
| table = constants.POWERUP_DROP_TABLE | |
| total = sum(weight for _, weight in table) | |
| r = random.uniform(0, total) | |
| cumulative = 0.0 | |
| for name, weight in table: | |
| cumulative += weight | |
| if r <= cumulative: | |
| return name | |
| return table[-1][0] | |
| class Item(pygame.sprite.Sprite): | |
| """Collectible item dropped from asteroids or enemies.""" | |
| ICONS: Dict[str, pygame.Surface] = {} | |
| def __init__(self, item_type: ItemType, pos: Tuple[float, float]): | |
| super().__init__() | |
| spec = ITEM_SPECS.get(item_type, ItemSpec((200, 200, 200), item_type)) | |
| self.item_type = item_type | |
| icon = self.ICONS.get(item_type) | |
| if icon: | |
| self.image = pygame.transform.scale(icon, (18, 18)) | |
| else: | |
| self.image = pygame.Surface((14, 14), pygame.SRCALPHA) | |
| self.image.fill(spec.color) | |
| font = pygame.font.Font(None, 12) | |
| text = font.render(spec.label, True, (0, 0, 0)) | |
| text_rect = text.get_rect(center=(7, 7)) | |
| self.image.blit(text, text_rect) | |
| self.rect = self.image.get_rect(center=pos) | |
| self.pos = pygame.Vector2(pos) | |
| self.vel = pygame.Vector2(-80, random.uniform(-20, 20)) # drift left with slight variation | |
| def update(self, dt: float): | |
| self.pos += self.vel * dt | |
| self.rect.center = (int(self.pos.x), int(self.pos.y)) | |
| class Inventory: | |
| """Simple inventory with capacity counting total items.""" | |
| def __init__(self, capacity: int = constants.INVENTORY_LIMIT): | |
| self.capacity = capacity | |
| self.counts: Dict[ItemType, int] = {} | |
| self.weapons: Dict[str, int] = {} | |
| self.upgrades: Dict[str, int] = {} | |
| self.owned_ships = {"salvager_one"} | |
| self.current_ship = "salvager_one" | |
| def total(self) -> int: | |
| return sum(self.counts.values()) | |
| def can_pick(self, amount: int = 1) -> bool: | |
| return True # unlimited inventory | |
| def add(self, item_type: ItemType, amount: int = 1) -> bool: | |
| if not self.can_pick(amount): | |
| return False | |
| self.counts[item_type] = self.counts.get(item_type, 0) + amount | |
| return True | |
| def get(self, item_type: ItemType) -> int: | |
| # Allow checking crafted weapons using snake_case keys for recipes | |
| weapon_key_map = { | |
| "laser_cannon": "Laser Cannon", | |
| "missile_launcher": "Missile Launcher", | |
| "shield_generator": "Shield Generator", | |
| "plasma_weapon": "Plasma Weapon", | |
| "destruction_ray": "Destruction Ray", | |
| } | |
| if item_type in weapon_key_map: | |
| return self.weapons.get(weapon_key_map[item_type], 0) | |
| return self.counts.get(item_type, 0) | |
| def add_weapon(self, weapon_name: str): | |
| self.weapons[weapon_name] = self.weapons.get(weapon_name, 0) + 1 | |
| def remove_weapon(self, weapon_name: str): | |
| if weapon_name not in self.weapons: | |
| return | |
| self.weapons[weapon_name] = max(0, self.weapons.get(weapon_name, 0) - 1) | |
| if self.weapons[weapon_name] <= 0: | |
| del self.weapons[weapon_name] | |