"""Le monde : obstacles (cylindres), cratères et soldats.""" from __future__ import annotations from dataclasses import dataclass, field import numpy as np from . import config as C @dataclass class Soldier: x: float y: float team: int # 0 = gauche (tire vers +x), 1 = droite (tire vers -x) alive: bool = True @property def dir(self) -> int: return 1 if self.team == 0 else -1 @dataclass class World: obstacles: np.ndarray # (M, 4) : x, y, rayon, hauteur soldiers: list[Soldier] = field(default_factory=list) craters: np.ndarray = field(default_factory=lambda: np.zeros((0, 3))) # (K, 3) : x, y, rayon # ------------------------------------------------------------------ génération @staticmethod def random(rng: np.random.Generator, team_sizes=(3, 3)) -> "World": n = int(rng.integers(C.OBST_COUNT[0], C.OBST_COUNT[1] + 1)) obs = np.column_stack([ rng.uniform(-22, 22, n), rng.uniform(-14, 14, n), rng.uniform(*C.OBST_RADIUS, n), rng.uniform(*C.OBST_HEIGHT, n), ]) w = World(obstacles=obs) for team, count in enumerate(team_sizes): for _ in range(count): w.soldiers.append(w._free_soldier(rng, team)) return w def _free_soldier(self, rng, team) -> Soldier: for _ in range(500): x = rng.uniform(3, 24) * (-1 if team == 0 else 1) y = rng.uniform(-13.5, 13.5) if self.is_free(x, y, C.SPAWN_MARGIN) and all( np.hypot(s.x - x, s.y - y) > 3.0 for s in self.soldiers ): return Soldier(x, y, team) # carte trop chargée : on retire l'obstacle le plus proche et on réessaie d = np.hypot(self.obstacles[:, 0] - x, self.obstacles[:, 1] - y) - self.obstacles[:, 2] self.obstacles = np.delete(self.obstacles, int(np.argmin(d)), axis=0) return self._free_soldier(rng, team) # ------------------------------------------------------------------ requêtes def is_free(self, x: float, y: float, margin: float = 0.0) -> bool: o = self.obstacles inside = np.hypot(o[:, 0] - x, o[:, 1] - y) < o[:, 2] + margin if not inside.any(): return True if len(self.craters) and margin == 0.0: c = self.craters if (np.hypot(c[:, 0] - x, c[:, 1] - y) < c[:, 2]).any(): return True return False def add_crater(self, x: float, y: float, r: float = C.CRATER_R) -> None: self.craters = np.vstack([self.craters, [x, y, r]]) def alive(self, team: int | None = None) -> list[int]: return [i for i, s in enumerate(self.soldiers) if s.alive and (team is None or s.team == team)] def copy(self) -> "World": return World( obstacles=self.obstacles.copy(), soldiers=[Soldier(s.x, s.y, s.team, s.alive) for s in self.soldiers], craters=self.craters.copy(), )