Foxgrad's picture
Add game engine and bots
3304d77 verified
Raw History Blame Contribute Delete
3.05 kB
"""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(),
)