diff --git a/state_loader.py b/state_loader.py new file mode 100644 index 0000000..72619ca --- /dev/null +++ b/state_loader.py @@ -0,0 +1,120 @@ + + +import json + +from typing import Dict, List, Any + + + +class GameState: + + def __init__(self): + + self.map_tiles = [] + + self.resources = {} + + self.entities = [] + + self.camera = {'x': 0, 'y': 0, 'zoom': 1.0} + + self.player_data = {} + + + +class StateLoader: + + def __init__(self): + + pass + + + + def load_level(self, path: str) -> GameState: + + try: + + with open(path, 'r') as file: + + data = json.load(file) + + except FileNotFoundError: + + raise FileNotFoundError(f"Level file not found: {path}") + + except json.JSONDecodeError: + + raise ValueError(f"Invalid JSON in level file: {path}") + + + + game_state = GameState() + + game_state.map_tiles = self.parse_map(data.get('map', {})) + + game_state.resources = data.get('resources', {}) + + game_state.entities = data.get('entities', []) + + game_state.player_data = data.get('player', {}) + + game_state.camera = data.get('camera', {'x': 0, 'y': 0, 'zoom': 1.0}) + + + + return game_state + + + + def parse_map(self, data: Dict[str, Any]) -> List[List[Dict]]: + + map_data = data.get('tiles', []) + + map_width = data.get('width', 0) + + map_height = data.get('height', 0) + + + + if not map_data or map_width == 0 or map_height == 0: + + return [] + + + + tiles = [] + + for y in range(map_height): + + row = [] + + for x in range(map_width): + + index = y * map_width + x + + if index < len(map_data): + + tile_data = map_data[index] + + row.append({ + + 'type': tile_data.get('type', 'grass'), + + 'walkable': tile_data.get('walkable', True), + + 'buildable': tile_data.get('buildable', True), + + 'resources': tile_data.get('resources', {}) + + }) + + else: + + row.append({'type': 'grass', 'walkable': True, 'buildable': True, 'resources': {}}) + + tiles.append(row) + + + + return tiles + diff --git a/state_updater.py b/state_updater.py new file mode 100644 index 0000000..2dd1a89 --- /dev/null +++ b/state_updater.py @@ -0,0 +1,170 @@ + + +from typing import List, Dict, Any + + + +class StateUpdater: + + def __init__(self): + + pass + + + + def apply_commands(self, state, commands: List[Dict]) -> List[Dict]: + + actions = [] + + for command in commands: + + action = self._process_command(state, command) + + if action: + + actions.append(action) + + return actions + + + + def tick(self, state, dt: float) -> None: + + self._update_movements(state, dt) + + self._update_entities(state, dt) + + self._update_resources(state, dt) + + + + def _process_command(self, state, command: Dict) -> Dict: + + cmd_type = command.get('type') + + + + if cmd_type == 'select': + + return {'type': 'selection_changed', 'world_pos': command.get('world_pos')} + + + + elif cmd_type == 'move': + + if hasattr(state, 'selected_entities') and state.selected_entities: + + for entity_id in state.selected_entities: + + entity = self._find_entity_by_id(state, entity_id) + + if entity and entity.get('movable', False): + + entity['target_x'] = command['world_pos'][0] + + entity['target_y'] = command['world_pos'][1] + + return {'type': 'move_ordered', 'target_pos': command.get('world_pos')} + + + + elif cmd_type == 'build': + + building_type = command.get('building_type') + + return {'type': 'build_started', 'building_type': building_type} + + + + elif cmd_type == 'gather': + + return {'type': 'gather_ordered'} + + + + return None + + + + def _update_movements(self, state, dt: float) -> None: + + if not hasattr(state, 'entities'): + + return + + + + for entity in state.entities: + + if (entity.get('movable', False) and + + 'target_x' in entity and 'target_y' in entity and + + 'x' in entity and 'y' in entity): + + + + dx = entity['target_x'] - entity['x'] + + dy = entity['target_y'] - entity['y'] + + distance = (dx**2 + dy**2)**0.5 + + + + if distance > 1.0: + + speed = entity.get('speed', 50.0) + + move_dist = speed * dt + + if move_dist > distance: + + entity['x'] = entity['target_x'] + + entity['y'] = entity['target_y'] + + else: + + entity['x'] += (dx / distance) * move_dist + + entity['y'] += (dy / distance) * move_dist + + + + def _update_entities(self, state, dt: float) -> None: + + if hasattr(state, 'entities'): + + for entity in state.entities: + + if 'health' in entity and entity['health'] <= 0: + + state.entities.remove(entity) + + + + def _update_resources(self, state, dt: float) -> None: + + if hasattr(state, 'resources'): + + for resource_type, amount in state.resources.items(): + + if isinstance(amount, (int, float)): + + state.resources[resource_type] = max(0, amount) + + + + def _find_entity_by_id(self, state, entity_id: int): + + if hasattr(state, 'entities'): + + for entity in state.entities: + + if entity.get('id') == entity_id: + + return entity + + return None + diff --git a/ui_panels.py b/ui_panels.py new file mode 100644 index 0000000..0b2be9c --- /dev/null +++ b/ui_panels.py @@ -0,0 +1,422 @@ + + +import pygame + +from typing import List, Dict + + + +class UIPanels: + + def __init__(self, screen_width: int, screen_height: int): + + self.screen_width = screen_width + + self.screen_height = screen_height + + self.ui_commands = [] + + self.ui_state = { + + 'selected_units': [], + + 'build_queue': [], + + 'show_unit_panel': False, + + 'resources': {}, + + 'unit_panel_data': [] + + } + + + + def update(self, snapshot: Dict, input_commands: List[Dict]) -> List[Dict]: + + self.ui_commands = [] + + + + for cmd in input_commands: + + if cmd.get('type') == 'select': + + self._handle_selection(snapshot, cmd) + + elif cmd.get('type') == 'build': + + self._handle_build_command(cmd) + + + + self._update_resource_bar(snapshot) + + self._update_unit_panel(snapshot) + + self._update_build_queue(snapshot) + + + + return self.ui_commands + + + + def draw(self, surface: pygame.Surface) -> None: + + self._draw_resource_bar(surface) + + self._draw_unit_panel(surface) + + self._draw_build_queue(surface) + + + + def handle_click(self, pos: tuple) -> None: + + if self._is_build_queue_click(pos): + + self._handle_build_queue_click(pos) + + elif self._is_unit_panel_click(pos): + + self._handle_unit_panel_click(pos) + + + + def _handle_selection(self, snapshot: Dict, command: Dict) -> None: + + self.ui_state['selected_units'] = [] + + world_pos = command.get('world_pos', (0, 0)) + + entities = snapshot.get('entities', []) + + + + for entity in entities: + + if self._is_entity_at_position(entity, world_pos): + + if entity.get('owner') == 'player': + + self.ui_state['selected_units'].append(entity['id']) + + + + self.ui_state['show_unit_panel'] = len(self.ui_state['selected_units']) > 0 + + + + def _handle_build_command(self, command: Dict) -> None: + + building_type = command.get('building_type') + + if building_type: + + self.ui_commands.append({ + + 'type': 'create_entity', + + 'entity_type': building_type, + + 'position': self._get_default_build_position() + + }) + + + + def _update_resource_bar(self, snapshot: Dict) -> None: + + self.ui_state['resources'] = snapshot.get('resources', {}) + + + + def _update_unit_panel(self, snapshot: Dict) -> None: + + if not self.ui_state['show_unit_panel']: + + return + + + + selected_units = [] + + entities = snapshot.get('entities', []) + + for entity_id in self.ui_state['selected_units']: + + for entity in entities: + + if entity.get('id') == entity_id: + + selected_units.append(entity) + + break + + + + self.ui_state['unit_panel_data'] = selected_units + + + + def _update_build_queue(self, snapshot: Dict) -> None: + + entities = snapshot.get('entities', []) + + self.ui_state['build_queue'] = [entity for entity in entities if entity.get('build_progress', 1) < 1] + + + + def _draw_resource_bar(self, surface: pygame.Surface) -> None: + + bar_height = 40 + + bar_rect = pygame.Rect(0, 0, self.screen_width, bar_height) + + pygame.draw.rect(surface, (50, 50, 80), bar_rect) + + pygame.draw.rect(surface, (100, 100, 150), bar_rect, 2) + + + + font = pygame.font.Font(None, 24) + + minerals = self.ui_state['resources'].get('minerals', 0) + + gas = self.ui_state['resources'].get('gas', 0) + + resources_text = f"Resources: {minerals} Minerals | {gas} Gas" + + text_surface = font.render(resources_text, True, (255, 255, 255)) + + surface.blit(text_surface, (10, 10)) + + + + def _draw_unit_panel(self, surface: pygame.Surface) -> None: + + if not self.ui_state['show_unit_panel']: + + return + + + + panel_width = 300 + + panel_height = 200 + + panel_x = self.screen_width - panel_width + + panel_y = self.screen_height - panel_height + + + + panel_rect = pygame.Rect(panel_x, panel_y, panel_width, panel_height) + + pygame.draw.rect(surface, (60, 60, 90), panel_rect) + + pygame.draw.rect(surface, (100, 100, 150), panel_rect, 2) + + + + font = pygame.font.Font(None, 20) + + title_text = f"Selected Units: {len(self.ui_state.get('unit_panel_data', []))}" + + title_surface = font.render(title_text, True, (255, 255, 255)) + + surface.blit(title_surface, (panel_x + 10, panel_y + 10)) + + + + if self.ui_state.get('unit_panel_data'): + + unit = self.ui_state['unit_panel_data'][0] + + unit_info = f"Type: {unit.get('name', 'Unknown')} | Health: {unit.get('health', 0)}/{unit.get('max_health', 0)}" + + info_surface = font.render(unit_info, True, (200, 200, 200)) + + surface.blit(info_surface, (panel_x + 10, panel_y + 40)) + + + + if unit.get('type') == 'unit': + + build_button = pygame.Rect(panel_x + 10, panel_y + 70, 80, 30) + + pygame.draw.rect(surface, (80, 80, 120), build_button) + + build_text = font.render("Build", True, (255, 255, 255)) + + surface.blit(build_text, (panel_x + 20, panel_y + 75)) + + + + def _draw_build_queue(self, surface: pygame.Surface) -> None: + + panel_width = 200 + + panel_height = 150 + + panel_x = 0 + + panel_y = self.screen_height - panel_height + + + + panel_rect = pygame.Rect(panel_x, panel_y, panel_width, panel_height) + + pygame.draw.rect(surface, (60, 60, 90), panel_rect) + + pygame.draw.rect(surface, (100, 100, 150), panel_rect, 2) + + + + font = pygame.font.Font(None, 20) + + title_text = "Build Queue" + + title_surface = font.render(title_text, True, (255, 255, 255)) + + surface.blit(title_surface, (panel_x + 10, panel_y + 10)) + + + + build_queue = self.ui_state.get('build_queue', []) + + for i, entity in enumerate(build_queue[:3]): + + progress = entity.get('build_progress', 0) / entity.get('build_time', 1) + + item_text = f"{entity.get('name', 'Unknown')}: {progress:.0%}" + + item_surface = font.render(item_text, True, (200, 200, 200)) + + surface.blit(item_surface, (panel_x + 10, panel_y + 40 + i * 25)) + + + + def _is_entity_at_position(self, entity: Dict, world_pos: tuple) -> bool: + + if 'x' not in entity or 'y' not in entity: + + return False + + + + dx = entity['x'] - world_pos[0] + + dy = entity['y'] - world_pos[1] + + distance = (dx*dx + dy*dy) ** 0.5 + + + + return distance < 30 + + + + def _get_default_build_position(self) -> tuple: + + return (self.screen_width // 2, self.screen_height // 2) + + + + def _is_build_queue_click(self, pos: tuple) -> bool: + + panel_width = 200 + + panel_height = 150 + + panel_x = 0 + + panel_y = self.screen_height - panel_height + + panel_rect = pygame.Rect(panel_x, panel_y, panel_width, panel_height) + + return panel_rect.collidepoint(pos) + + + + def _is_unit_panel_click(self, pos: tuple) -> bool: + + if not self.ui_state['show_unit_panel']: + + return False + + panel_width = 300 + + panel_height = 200 + + panel_x = self.screen_width - panel_width + + panel_y = self.screen_height - panel_height + + panel_rect = pygame.Rect(panel_x, panel_y, panel_width, panel_height) + + return panel_rect.collidepoint(pos) + + + + def _handle_build_queue_click(self, pos: tuple) -> None: + + panel_width = 200 + + panel_height = 150 + + panel_x = 0 + + panel_y = self.screen_height - panel_height + + + + relative_y = pos[1] - panel_y + + if 40 <= relative_y <= 115: + + item_index = (relative_y - 40) // 25 + + build_queue = self.ui_state.get('build_queue', []) + + if item_index < len(build_queue): + + self.ui_commands.append({ + + 'type': 'cancel_build', + + 'entity_id': build_queue[item_index]['id'] + + }) + + + + def _handle_unit_panel_click(self, pos: tuple) -> None: + + panel_width = 300 + + panel_height = 200 + + panel_x = self.screen_width - panel_width + + panel_y = self.screen_height - panel_height + + + + relative_x = pos[0] - panel_x + + relative_y = pos[1] - panel_y + + + + if 10 <= relative_x <= 90 and 70 <= relative_y <= 100: + + self.ui_commands.append({ + + 'type': 'build', + + 'building_type': 'barracks' + + }) +