import pygame import math import random from submodules.robot_controller_base.robot_controller import RobotController class Fire: def __init__(self, x, y, radius): self.x = x self.y = y self.radius = radius class DummySimRobot(RobotController): def __init__(self, config, stats_path='./stats'): super().__init__( fire_ext_max_capacity=config.INITIAL_FIRE_CHARGE, collision_damage=config.COLLISION_DAMAGE, k_sof=config.SCORE_PER_FIRE, k_cas=1, k_asset=1, k_hp=1, k_charge=1, fall_damage=10 ) pygame.init() self.width = config.MAP_WIDTH self.height = config.MAP_HEIGHT self.screen = pygame.display.set_mode((self.width, self.height)) pygame.display.set_caption("Robot Simulator – Extinguish Fires") self.clock = pygame.time.Clock() self.font = pygame.font.SysFont(None, 28) border_thickness = config.BORDER_THICKNESS if border_thickness > 0: border_obstacles = [ (0, 0, self.width, border_thickness), (0, self.height - border_thickness, self.width, border_thickness), (0, 0, border_thickness, self.height), (self.width - border_thickness, 0, border_thickness, self.height) ] self.obstacles = list(config.MAP_OBSTACLES) + border_obstacles else: self.obstacles = list(config.MAP_OBSTACLES) self.start = config.START_POS self.finish = config.FINISH_POS self.robot_radius = config.ROBOT_RADIUS self.robot_image = None if config.ROBOT_IMAGE_PATH: try: self.robot_image = pygame.image.load(config.ROBOT_IMAGE_PATH) self.robot_image = pygame.transform.scale(self.robot_image, (self.robot_radius*2, self.robot_radius*2)) except: pass self.extinguish_radius = config.EXTINGUISH_RADIUS self.extinguish_angle = math.radians(config.EXTINGUISH_ANGLE) self.score_per_fire = config.SCORE_PER_FIRE self.num_fires = config.NUM_FIRES self.fire_radius = config.FIRE_RADIUS self.extinguish_active = False self.extinguish_timer = 0 self.extinguish_cooldown = 0 self.invincibility_timer = 0 self.x, self.y = self.start self.angle = 0.0 self.linear_speed = 0.0 self.angular_speed = 0.0 self.game_over = False self.won = False self.fires = [] self._generate_fires() # Параметры для сохранения статистики self.stats_path = stats_path self._stats_saved = False self._last_stats_path = None def _generate_fires(self): self.fires.clear() attempts = 0 margin = 30 # минимальное расстояние между центрами костров while len(self.fires) < self.num_fires and attempts < 2000: fx = random.randint(self.robot_radius + 10, self.width - self.robot_radius - 10) fy = random.randint(self.robot_radius + 10, self.height - self.robot_radius - 10) # Проверка: не на препятствии if self._point_collides_with_obstacle(fx, fy, self.robot_radius): attempts += 1 continue # Проверка: не слишком близко к старту if math.hypot(fx - self.start[0], fy - self.start[1]) < self.robot_radius * 3: attempts += 1 continue # Проверка: не слишком близко к финишу if math.hypot(fx - self.finish[0], fy - self.finish[1]) < self.robot_radius * 3: attempts += 1 continue # Проверка: не пересекается с уже существующими кострами overlap = False for fire in self.fires: if math.hypot(fx - fire.x, fy - fire.y) < 2 * self.fire_radius + margin: overlap = True break if overlap: attempts += 1 continue self.fires.append(Fire(fx, fy, self.fire_radius)) attempts += 1 print(f"Сгенерировано {len(self.fires)} очагов возгорания") def _point_collides_with_obstacle(self, x, y, radius): for ox, oy, ow, oh in self.obstacles: closest_x = max(ox, min(x, ox + ow)) closest_y = max(oy, min(y, oy + oh)) if math.hypot(x - closest_x, y - closest_y) < radius: return True return False def _is_fire_in_front(self, fire): """Проверяет, находится ли огонь в зоне тушения""" dx = fire.x - self.x dy = fire.y - self.y dist = math.hypot(dx, dy) if dist > self.extinguish_radius: return False angle_to_fire = math.atan2(dy, dx) angle_diff = angle_to_fire - self.angle angle_diff = (angle_diff + math.pi) % (2*math.pi) - math.pi return abs(angle_diff) <= self.extinguish_angle / 2 def send_speed_cmd(self, v, w): """Отправка команды скорости с проверкой заряда""" self._check_time_init() if self._charge_points == 0: return 0, 0 v = min(max(-1, v), 1) w = min(max(-1, w), 1) self.linear_speed = v self.angular_speed = w return v, w def send_fire_ext_burst_cmd(self): """Команда тушения огня""" self._check_time_init() if self.extinguish_cooldown > 0 or self.extinguish_active: return False if self._fire_ext_capacity <= 0: return False self.extinguish_active = True self.extinguish_timer = 30 self.extinguish_cooldown = 10 self._fire_ext_capacity -= 1 extinguished = False for fire in self.fires[:]: if self._is_fire_in_front(fire): self.fires.remove(fire) self._register_exted_sof({ 'x': fire.x, 'y': fire.y, 'radius': fire.radius }) extinguished = True break return extinguished def send_fire_ext_pose_cmd(self, horizontal_pose, vertical_pose): """Команда позиции огнетушителя""" self._check_time_init() raise NotImplementedError("Fire extinguish not implemented in DummySimRobot") def send_pick_asset_cmd(self): """Команда подбора объекта""" self._check_time_init() raise NotImplementedError("Pick asset not implemented in DummySimRobot") def send_lights_cmd(self): """Команда включения света""" self._check_time_init() raise NotImplementedError("Turn on the light not implemented in DummySimRobot") def send_fall_reset_cmd(self): """Команда сброса после падения""" self._check_time_init() raise NotImplementedError("Fall can't be implemented DummySimRobot") def update(self, dt=1/30.0): if self.game_over or self.won: return # Уменьшение заряда if self.linear_speed != 0: self._decrease_charge(abs(self.linear_speed), dt) # Обновление таймеров if self.invincibility_timer > 0: self.invincibility_timer -= 1 if self.extinguish_timer > 0: self.extinguish_timer -= 1 if self.extinguish_timer == 0: self.extinguish_active = False if self.extinguish_cooldown > 0: self.extinguish_cooldown -= 1 # Движение self.angle += self.angular_speed * dt * 2 dx = self.linear_speed * math.cos(self.angle) * dt * 100 dy = self.linear_speed * math.sin(self.angle) * dt * 100 new_x = self.x + dx new_y = self.y + dy # Проверка: столкновение if self._point_collides_with_obstacle(new_x, new_y, self.robot_radius): if self.invincibility_timer == 0: self._register_collision() self.invincibility_timer = 30 else: self.x, self.y = new_x, new_y # Проверка победы/поражения if math.hypot(self.x - self.finish[0], self.y - self.finish[1]) < self.robot_radius: self.won = True self._save_statistics() if self._hit_points <= 0: self.game_over = True self._save_statistics() if self._charge_points <= 0: self.game_over = True self._save_statistics() def draw(self): """Отрисовка симуляции""" self.screen.fill((255,255,255)) # Препятствия for ox, oy, ow, oh in self.obstacles: pygame.draw.rect(self.screen, (100,100,100), (ox, oy, ow, oh)) # Финиш pygame.draw.circle(self.screen, (0,200,0), (int(self.finish[0]), int(self.finish[1])), self.robot_radius, 3) # Костры for fire in self.fires: pygame.draw.circle(self.screen, (255,0,0), (int(fire.x), int(fire.y)), fire.radius) pygame.draw.circle(self.screen, (255,100,0), (int(fire.x), int(fire.y)), fire.radius-3) # Конус тушения if self.extinguish_active: cone_surface = pygame.Surface((self.width, self.height), pygame.SRCALPHA) num_points = 20 points = [(self.x, self.y)] half_angle = self.extinguish_angle / 2 cone_len = self.extinguish_radius for i in range(num_points + 1): angle = self.angle - half_angle + (self.extinguish_angle) * (i / num_points) x = self.x + cone_len * math.cos(angle) y = self.y + cone_len * math.sin(angle) points.append((x, y)) if len(points) > 2: pygame.draw.polygon(cone_surface, (0, 200, 255, 80), points) self.screen.blit(cone_surface, (0, 0)) # Робот if self.invincibility_timer > 0 and (self.invincibility_timer % 6 < 3): pass # Мигание else: if self.robot_image: rotated = pygame.transform.rotate(self.robot_image, -math.degrees(self.angle)) rect = rotated.get_rect(center=(int(self.x), int(self.y))) self.screen.blit(rotated, rect) else: nose = (self.x + self.robot_radius * math.cos(self.angle), self.y + self.robot_radius * math.sin(self.angle)) left = (self.x + self.robot_radius * math.cos(self.angle + 2.5), self.y + self.robot_radius * math.sin(self.angle + 2.5)) right = (self.x + self.robot_radius * math.cos(self.angle - 2.5), self.y + self.robot_radius * math.sin(self.angle - 2.5)) pygame.draw.polygon(self.screen, (0,0,255), [nose, left, right]) # Статистика hp_text = self.font.render(f"HP: {self._hit_points}", True, (0,0,0)) charge_text = self.font.render(f"Charge: {self._charge_points:.1f}", True, (0,0,0)) fire_charge_text = self.font.render(f"Fire charges: {self._fire_ext_capacity}", True, (0,0,0)) score_text = self.font.render(f"Score: {self.get_score():.2f}", True, (0,0,0)) #fires_text = self.font.render(f"Fires left: {len(self.fires)}", True, (0,0,0)) self.screen.blit(hp_text, (10, 10)) self.screen.blit(charge_text, (10, 40)) self.screen.blit(fire_charge_text, (10, 70)) self.screen.blit(score_text, (10, 100)) # Конец игры if self.game_over: msg = self.font.render("GAME OVER – Press R to restart", True, (255,0,0)) self.screen.blit(msg, (self.width//2 - 150, self.height//2)) elif self.won: msg = self.font.render("YOU WIN! – Press R to restart", True, (0,100,0)) self.screen.blit(msg, (self.width//2 - 120, self.height//2)) pygame.display.flip() def _save_statistics(self): """Сохраняет статистику при окончании игры""" if not self._stats_saved: self._last_stats_path = self.dump_stats(save_path=self.stats_path) self._stats_saved = True print(f"\n{'='*50}") print(f"GAME {'WON' if self.won else 'OVER'}") print(f"Stats saved: {self._last_stats_path}") print(self.get_str_status()) print(f"{'='*50}\n") def handle_events(self): """Обработка событий pygame""" for event in pygame.event.get(): if event.type == pygame.QUIT: return False if event.type == pygame.KEYDOWN: if event.key == pygame.K_r: self.restart() elif event.key == pygame.K_BACKSPACE: self.reset_position() return True def step(self): """Один шаг симуляции""" if not self.handle_events(): return False self.update() self.draw() self.clock.tick(30) return True def restart(self): """Перезапуск игры""" self.x, self.y = self.start self.angle = 0.0 self.linear_speed = 0.0 self.angular_speed = 0.0 # Сброс основных состояний self._hit_points = self._start_hp self._fire_ext_capacity = self._fire_ext_max_capacity self._charge_points = self._start_charge self._collisions.clear() self._exted_sofs.clear() self._found_casualty.clear() self._found_assets.clear() self._falls.clear() self._start_time = None # Сброс состояния симуляции self.game_over = False self.won = False self.invincibility_timer = 0 self.extinguish_active = False self.extinguish_timer = 0 self.extinguish_cooldown = 0 self._stats_saved = False self._last_stats_path = None # Новая генерация костров self._generate_fires() def reset_position(self): """Сброс позиции робота""" if self.game_over or self.won: return self.x, self.y = self.start self.angle = 0.0 self.linear_speed = 0.0 self.angular_speed = 0.0 def quit(self): """Завершение pygame""" pygame.quit()