Map.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  1. <?php
  2. /**
  3. * Classe Tile
  4. *
  5. * Représente une tuile avec des propriétés arbitraires.
  6. */
  7. if (!class_exists('Tile')) {
  8. class Tile
  9. {
  10. /**
  11. * @var array Les propriétés de la tuile
  12. */
  13. private array $properties;
  14. /**
  15. * Constructeur d'une tuile
  16. *
  17. * @param array $properties Les propriétés initiales de la tuile
  18. */
  19. public function __construct(array $properties = [])
  20. {
  21. $this->properties = $properties;
  22. }
  23. /**
  24. * Définit une propriété
  25. *
  26. * @param string $key La clé de la propriété
  27. * @param mixed $value La valeur de la propriété
  28. */
  29. public function setProperty(string $key, $value): void
  30. {
  31. $this->properties[$key] = $value;
  32. }
  33. /**
  34. * Récupère une propriété
  35. *
  36. * @param string $key La clé de la propriété
  37. * @param mixed $default La valeur par défaut si la propriété n'existe pas
  38. * @return mixed La valeur de la propriété
  39. */
  40. public function getProperty(string $key, $default = null)
  41. {
  42. return $this->properties[$key] ?? $default;
  43. }
  44. /**
  45. * Vérifie si une propriété existe
  46. *
  47. * @param string $key La clé de la propriété
  48. * @return bool True si la propriété existe
  49. */
  50. public function hasProperty(string $key): bool
  51. {
  52. return array_key_exists($key, $this->properties);
  53. }
  54. /**
  55. * Supprime une propriété
  56. *
  57. * @param string $key La clé de la propriété
  58. */
  59. public function removeProperty(string $key): void
  60. {
  61. unset($this->properties[$key]);
  62. }
  63. /**
  64. * Récupère toutes les propriétés
  65. *
  66. * @return array Toutes les propriétés
  67. */
  68. public function getAllProperties(): array
  69. {
  70. return $this->properties;
  71. }
  72. /**
  73. * Définit toutes les propriétés
  74. *
  75. * @param array $properties Les nouvelles propriétés
  76. */
  77. public function setAllProperties(array $properties): void
  78. {
  79. $this->properties = $properties;
  80. }
  81. /**
  82. * Vérifie si la tuile est vide (aucune propriété)
  83. *
  84. * @return bool True si la tuile est vide
  85. */
  86. public function isEmpty(): bool
  87. {
  88. return empty($this->properties);
  89. }
  90. /**
  91. * Convertit la tuile en tableau pour la sérialisation
  92. *
  93. * @return array Les données de la tuile
  94. */
  95. public function toArray(): array
  96. {
  97. return $this->properties;
  98. }
  99. /**
  100. * Crée une tuile depuis un tableau
  101. *
  102. * @param array $data Les données de la tuile
  103. * @return Tile La tuile créée
  104. */
  105. public static function fromArray(array $data): Tile
  106. {
  107. return new self($data);
  108. }
  109. /**
  110. * Méthodes d'accès rapide pour les propriétés communes
  111. */
  112. /**
  113. * Récupère le type de la tuile
  114. *
  115. * @return string|null Le type ou null
  116. */
  117. public function getType(): ?string
  118. {
  119. return $this->getProperty('type');
  120. }
  121. /**
  122. * Définit le type de la tuile
  123. *
  124. * @param string $type Le type
  125. */
  126. public function setType(string $type): void
  127. {
  128. $this->setProperty('type', $type);
  129. }
  130. /**
  131. * Récupère les ressources de la tuile
  132. *
  133. * @return array Les ressources
  134. */
  135. public function getResources(): array
  136. {
  137. return $this->getProperty('resources', []);
  138. }
  139. /**
  140. * Définit les ressources de la tuile
  141. *
  142. * @param array $resources Les ressources
  143. */
  144. public function setResources(array $resources): void
  145. {
  146. $this->setProperty('resources', $resources);
  147. }
  148. /**
  149. * Récupère la température de la tuile
  150. *
  151. * @return float|null La température ou null
  152. */
  153. public function getTemperature(): ?float
  154. {
  155. return $this->getProperty('temperature');
  156. }
  157. /**
  158. * Définit la température de la tuile
  159. *
  160. * @param float $temperature La température
  161. */
  162. public function setTemperature(float $temperature): void
  163. {
  164. $this->setProperty('temperature', $temperature);
  165. }
  166. /**
  167. * Récupère l'élévation de la tuile
  168. *
  169. * @return int|null L'élévation ou null
  170. */
  171. public function getElevation(): ?int
  172. {
  173. return $this->getProperty('elevation');
  174. }
  175. /**
  176. * Définit l'élévation de la tuile
  177. *
  178. * @param int $elevation L'élévation
  179. */
  180. public function setElevation(int $elevation): void
  181. {
  182. $this->setProperty('elevation', $elevation);
  183. }
  184. }
  185. }
  186. /**
  187. * Classe Map
  188. *
  189. * Représente une carte composée d'hexagones (damier hexagonal).
  190. * Chaque hexagone est une parcelle pouvant contenir une tuile avec des propriétés arbitraires.
  191. */
  192. if (!class_exists('Map')) {
  193. class Map
  194. {
  195. /**
  196. * @var array Les hexagones de la carte indexés par coordonnées [q][r]
  197. */
  198. private array $hexagons = [];
  199. /**
  200. * @var string Le nom de la carte
  201. */
  202. private string $name;
  203. /**
  204. * @var int La largeur de la carte (nombre d'hexagones en largeur)
  205. */
  206. private int $width;
  207. /**
  208. * @var int La hauteur de la carte (nombre d'hexagones en hauteur)
  209. */
  210. private int $height;
  211. /**
  212. * @var string La date de création de la carte
  213. */
  214. private string $createdAt;
  215. /**
  216. * @var string La date de dernière modification
  217. */
  218. private string $updatedAt;
  219. /**
  220. * Constructeur de la carte
  221. *
  222. * @param string $name Le nom de la carte
  223. * @param int $width La largeur de la carte (en nombre d'hexagones)
  224. * @param int $height La hauteur de la carte (en nombre d'hexagones)
  225. */
  226. public function __construct(string $name, int $width = 10, int $height = 10)
  227. {
  228. $this->name = $name;
  229. $this->width = $width;
  230. $this->height = $height;
  231. $this->createdAt = date('Y-m-d H:i:s');
  232. $this->updatedAt = date('Y-m-d H:i:s');
  233. // Initialiser la carte avec des hexagones vides
  234. $this->initializeMap();
  235. }
  236. /**
  237. * Initialise la carte avec des hexagones vides
  238. */
  239. private function initializeMap(): void
  240. {
  241. for ($q = 0; $q < $this->width; $q++) {
  242. for ($r = 0; $r < $this->height; $r++) {
  243. $this->hexagons[$q][$r] = new Hexagon($q, $r, new Tile(['type' => 'empty']));
  244. }
  245. }
  246. }
  247. /**
  248. * Définit la tuile d'un hexagone
  249. *
  250. * @param int $q Coordonnée Q de l'hexagone
  251. * @param int $r Coordonnée R de l'hexagone
  252. * @param Tile $tile La tuile à placer
  253. * @return bool True si l'hexagone a été modifié, false sinon
  254. */
  255. public function setTile(int $q, int $r, Tile $tile): bool
  256. {
  257. if (!$this->isValidCoordinate($q, $r)) {
  258. return false;
  259. }
  260. $this->hexagons[$q][$r]->setTile($tile);
  261. $this->updatedAt = date('Y-m-d H:i:s');
  262. return true;
  263. }
  264. /**
  265. * Récupère la tuile d'un hexagone
  266. *
  267. * @param int $q Coordonnée Q de l'hexagone
  268. * @param int $r Coordonnée R de l'hexagone
  269. * @return Tile|null La tuile ou null si les coordonnées sont invalides
  270. */
  271. public function getTile(int $q, int $r): ?Tile
  272. {
  273. if (!$this->isValidCoordinate($q, $r)) {
  274. return null;
  275. }
  276. return $this->hexagons[$q][$r]->getTile();
  277. }
  278. /**
  279. * Récupère un hexagone
  280. *
  281. * @param int $q Coordonnée Q de l'hexagone
  282. * @param int $r Coordonnée R de l'hexagone
  283. * @return Hexagon|null L'hexagone ou null si les coordonnées sont invalides
  284. */
  285. public function getHexagon(int $q, int $r): ?Hexagon
  286. {
  287. if (!$this->isValidCoordinate($q, $r)) {
  288. return null;
  289. }
  290. return $this->hexagons[$q][$r];
  291. }
  292. /**
  293. * Vérifie si les coordonnées sont valides
  294. *
  295. * @param int $q Coordonnée Q
  296. * @param int $r Coordonnée R
  297. * @return bool True si les coordonnées sont valides
  298. */
  299. public function isValidCoordinate(int $q, int $r): bool
  300. {
  301. return isset($this->hexagons[$q][$r]);
  302. }
  303. /**
  304. * Récupère tous les hexagones de la carte
  305. *
  306. * @return array Les hexagones indexés par [q][r]
  307. */
  308. public function getAllHexagons(): array
  309. {
  310. return $this->hexagons;
  311. }
  312. /**
  313. * Récupère les hexagones d'une ligne spécifique
  314. *
  315. * @param int $q La coordonnée Q de la ligne
  316. * @return array|null Les hexagones de la ligne ou null si la ligne n'existe pas
  317. */
  318. public function getHexagonsRow(int $q): ?array
  319. {
  320. return $this->hexagons[$q] ?? null;
  321. }
  322. /**
  323. * Compte le nombre d'hexagones d'un certain type
  324. *
  325. * @param string $tileType Le type de tuile à compter
  326. * @return int Le nombre d'hexagones de ce type
  327. */
  328. public function countTiles(string $tileType): int
  329. {
  330. $count = 0;
  331. foreach ($this->hexagons as $row) {
  332. foreach ($row as $hexagon) {
  333. $tile = $hexagon->getTile();
  334. if ($tile && $tile->getType() === $tileType) {
  335. $count++;
  336. }
  337. }
  338. }
  339. return $count;
  340. }
  341. /**
  342. * Compte le nombre d'hexagones vides
  343. *
  344. * @return int Le nombre d'hexagones vides
  345. */
  346. public function countEmptyTiles(): int
  347. {
  348. $count = 0;
  349. foreach ($this->hexagons as $row) {
  350. foreach ($row as $hexagon) {
  351. if ($hexagon->isEmpty()) {
  352. $count++;
  353. }
  354. }
  355. }
  356. return $count;
  357. }
  358. /**
  359. * Récupère les statistiques de la carte
  360. *
  361. * @return array Les statistiques (nombre total d'hexagones, répartition par type)
  362. */
  363. public function getStatistics(): array
  364. {
  365. // Calculer le nombre réel d'hexagones présents (certains emplacements peuvent être absents
  366. // lorsqu'on construit une carte à partir d'un rayon hexagonal)
  367. $totalHexagons = 0;
  368. foreach ($this->hexagons as $row) {
  369. $totalHexagons += count($row);
  370. }
  371. $tileTypeCounts = [];
  372. $emptyCount = $this->countEmptyTiles();
  373. // Compter les types de tuiles présents
  374. foreach ($this->hexagons as $row) {
  375. foreach ($row as $hexagon) {
  376. $tile = $hexagon->getTile();
  377. if ($tile && !$tile->isEmpty()) {
  378. $type = $tile->getType();
  379. if ($type !== null) {
  380. $tileTypeCounts[$type] = ($tileTypeCounts[$type] ?? 0) + 1;
  381. }
  382. }
  383. }
  384. }
  385. return [
  386. 'total_hexagons' => $totalHexagons,
  387. 'width' => $this->width,
  388. 'height' => $this->height,
  389. 'tile_type_counts' => $tileTypeCounts,
  390. 'empty_count' => $emptyCount,
  391. 'empty_percentage' => round(($emptyCount / $totalHexagons) * 100, 2),
  392. 'filled_count' => $totalHexagons - $emptyCount,
  393. 'filled_percentage' => round((($totalHexagons - $emptyCount) / $totalHexagons) * 100, 2)
  394. ];
  395. }
  396. /**
  397. * Crée une carte à partir d'un rayon hexagonal (axial coordinates)
  398. *
  399. * @param string $name
  400. * @param int $radius
  401. * @return Map
  402. */
  403. public static function fromRadius(string $name, int $radius): Map
  404. {
  405. // Bounding box size
  406. $size = 2 * $radius + 1;
  407. $map = new self($name, $size, $size);
  408. // Remplacer l'initialisation par une grille qui contient uniquement
  409. // les hexagones à l'intérieur du rayon. Les hexagones sont indexés
  410. // dans l'array par des indices positifs (offset) mais conservent
  411. // leurs coordonnées axiales originales dans l'objet Hexagon.
  412. $map->hexagons = [];
  413. for ($q = -$radius; $q <= $radius; $q++) {
  414. $r1 = max(-$radius, -$q - $radius);
  415. $r2 = min($radius, -$q + $radius);
  416. for ($r = $r1; $r <= $r2; $r++) {
  417. $qi = $q + $radius; // index pour stockage
  418. $ri = $r + $radius;
  419. if (!isset($map->hexagons[$qi])) $map->hexagons[$qi] = [];
  420. $map->hexagons[$qi][$ri] = new Hexagon($q, $r, new Tile(['type' => 'empty']));
  421. }
  422. }
  423. return $map;
  424. }
  425. /**
  426. * Convertit la carte en tableau pour la sérialisation JSON
  427. *
  428. * @return array Les données de la carte
  429. */
  430. public function toArray(): array
  431. {
  432. $hexagonsData = [];
  433. foreach ($this->hexagons as $q => $row) {
  434. foreach ($row as $r => $hexagon) {
  435. $hexagonsData[] = $hexagon->toArray();
  436. }
  437. }
  438. return [
  439. 'name' => $this->name,
  440. 'width' => $this->width,
  441. 'height' => $this->height,
  442. 'created_at' => $this->createdAt,
  443. 'updated_at' => $this->updatedAt,
  444. 'hexagons' => $hexagonsData
  445. ];
  446. }
  447. /**
  448. * Convertit la carte en JSON
  449. *
  450. * @param bool $prettyPrint True pour un JSON formaté
  451. * @return string Le JSON de la carte
  452. */
  453. public function toJson(bool $prettyPrint = false): string
  454. {
  455. return json_encode(
  456. $this->toArray(),
  457. $prettyPrint ? JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE : 0
  458. );
  459. }
  460. /**
  461. * Sauvegarde la carte dans un fichier JSON
  462. *
  463. * @param string $filePath Le chemin du fichier
  464. * @return bool True si la sauvegarde a réussi
  465. */
  466. public function saveToFile(string $filePath): bool
  467. {
  468. try {
  469. $dir = dirname($filePath);
  470. if (!is_dir($dir)) {
  471. mkdir($dir, 0755, true);
  472. }
  473. $json = $this->toJson(true);
  474. $result = @file_put_contents($filePath, $json);
  475. return $result !== false;
  476. } catch (Exception $e) {
  477. AppDebugger::log('Erreur lors de la sauvegarde de la carte: ' . $e->getMessage(), 'ERROR');
  478. return false;
  479. }
  480. }
  481. /**
  482. * Charge une carte depuis un tableau
  483. *
  484. * @param array $data Les données de la carte
  485. * @return Map|null La carte chargée ou null en cas d'erreur
  486. */
  487. public static function fromArray(array $data): ?Map
  488. {
  489. try {
  490. $map = new self($data['name'], $data['width'], $data['height']);
  491. $map->createdAt = $data['created_at'] ?? date('Y-m-d H:i:s');
  492. $map->updatedAt = $data['updated_at'] ?? date('Y-m-d H:i:s');
  493. // Charger les hexagones
  494. foreach ($data['hexagons'] as $hexagonData) {
  495. $q = $hexagonData['q'];
  496. $r = $hexagonData['r'];
  497. if ($map->isValidCoordinate($q, $r)) {
  498. $map->hexagons[$q][$r] = Hexagon::fromArray($hexagonData);
  499. }
  500. }
  501. return $map;
  502. } catch (Exception $e) {
  503. AppDebugger::log('Erreur lors du chargement de la carte depuis un tableau: ' . $e->getMessage(), 'ERROR');
  504. return null;
  505. }
  506. }
  507. /**
  508. * Charge une carte depuis un fichier JSON
  509. *
  510. * @param string $filePath Le chemin du fichier
  511. * @return Map|null La carte chargée ou null en cas d'erreur
  512. */
  513. public static function fromFile(string $filePath): ?Map
  514. {
  515. try {
  516. if (!file_exists($filePath)) {
  517. return null;
  518. }
  519. $json = file_get_contents($filePath);
  520. $data = json_decode($json, true);
  521. if (json_last_error() !== JSON_ERROR_NONE) {
  522. AppDebugger::log('Erreur JSON lors du chargement du fichier: ' . json_last_error_msg(), 'ERROR');
  523. return null;
  524. }
  525. return self::fromArray($data);
  526. } catch (Exception $e) {
  527. AppDebugger::log('Erreur lors du chargement de la carte depuis un fichier: ' . $e->getMessage(), 'ERROR');
  528. return null;
  529. }
  530. }
  531. // Getters
  532. public function getName(): string { return $this->name; }
  533. public function getWidth(): int { return $this->width; }
  534. public function getHeight(): int { return $this->height; }
  535. public function getCreatedAt(): string { return $this->createdAt; }
  536. public function getUpdatedAt(): string { return $this->updatedAt; }
  537. // Setters
  538. public function setName(string $name): void
  539. {
  540. $this->name = $name;
  541. $this->updatedAt = date('Y-m-d H:i:s');
  542. }
  543. }
  544. }
  545. /**
  546. * Classe Hexagon
  547. *
  548. * Représente un hexagone individuel dans la carte.
  549. */
  550. if (!class_exists('Hexagon')) {
  551. class Hexagon
  552. {
  553. /**
  554. * @var int Coordonnée Q (axe horizontal)
  555. */
  556. private int $q;
  557. /**
  558. * @var int Coordonnée R (axe diagonal)
  559. */
  560. private int $r;
  561. /**
  562. * @var Tile|null La tuile de l'hexagone
  563. */
  564. private ?Tile $tile;
  565. /**
  566. * Constructeur d'un hexagone
  567. *
  568. * @param int $q Coordonnée Q
  569. * @param int $r Coordonnée R
  570. * @param Tile|null $tile La tuile initiale (null pour vide)
  571. */
  572. public function __construct(int $q, int $r, ?Tile $tile = null)
  573. {
  574. $this->q = $q;
  575. $this->r = $r;
  576. $this->tile = $tile;
  577. }
  578. /**
  579. * Définit la tuile
  580. *
  581. * @param Tile|null $tile La nouvelle tuile (null pour vider)
  582. */
  583. public function setTile(?Tile $tile): void
  584. {
  585. $this->tile = $tile;
  586. }
  587. /**
  588. * Récupère la tuile
  589. *
  590. * @return Tile|null La tuile ou null si vide
  591. */
  592. public function getTile(): ?Tile
  593. {
  594. return $this->tile;
  595. }
  596. /**
  597. * Vérifie si l'hexagone est vide
  598. *
  599. * @return bool True si l'hexagone est vide
  600. */
  601. public function isEmpty(): bool
  602. {
  603. return $this->tile === null || $this->tile->isEmpty() || $this->tile->getType() === 'empty';
  604. }
  605. /**
  606. * Récupère le type de tuile (méthode de compatibilité)
  607. *
  608. * @return string|null Le type de tuile ou null
  609. */
  610. public function getTileType(): ?string
  611. {
  612. return $this->tile ? $this->tile->getType() : null;
  613. }
  614. /**
  615. * Définit le type de tuile (méthode de compatibilité)
  616. *
  617. * @param string $tileType Le type de tuile
  618. */
  619. public function setTileType(string $tileType): void
  620. {
  621. if ($this->tile === null) {
  622. $this->tile = new Tile();
  623. }
  624. $this->tile->setType($tileType);
  625. }
  626. /**
  627. * Calcule la coordonnée S (pour les coordonnées cubiques)
  628. *
  629. * @return int La coordonnée S
  630. */
  631. public function getS(): int
  632. {
  633. return -$this->q - $this->r;
  634. }
  635. /**
  636. * Calcule la distance à un autre hexagone
  637. *
  638. * @param Hexagon $other L'autre hexagone
  639. * @return int La distance
  640. */
  641. public function distanceTo(Hexagon $other): int
  642. {
  643. return (abs($this->q - $other->q) + abs($this->r - $other->r) + abs($this->getS() - $other->getS())) / 2;
  644. }
  645. /**
  646. * Récupère les hexagones voisins
  647. *
  648. * @return array Les directions vers les voisins [q, r]
  649. */
  650. public static function getNeighbors(): array
  651. {
  652. return [
  653. [1, 0], // Est
  654. [1, -1], // Nord-Est
  655. [0, -1], // Nord-Ouest
  656. [-1, 0], // Ouest
  657. [-1, 1], // Sud-Ouest
  658. [0, 1] // Sud-Est
  659. ];
  660. }
  661. /**
  662. * Convertit l'hexagone en tableau pour la sérialisation
  663. *
  664. * @return array Les données de l'hexagone
  665. */
  666. public function toArray(): array
  667. {
  668. return [
  669. 'q' => $this->q,
  670. 'r' => $this->r,
  671. 'tile' => $this->tile ? $this->tile->toArray() : null
  672. ];
  673. }
  674. /**
  675. * Crée un hexagone depuis un tableau
  676. *
  677. * @param array $data Les données de l'hexagone
  678. * @return Hexagon L'hexagone créé
  679. */
  680. public static function fromArray(array $data): Hexagon
  681. {
  682. $tile = null;
  683. if (isset($data['tile']) && $data['tile'] !== null) {
  684. $tile = Tile::fromArray($data['tile']);
  685. } elseif (isset($data['tile_type'])) {
  686. // Compatibilité avec l'ancien format
  687. $tile = new Tile(['type' => $data['tile_type']]);
  688. }
  689. return new self($data['q'], $data['r'], $tile);
  690. }
  691. // Getters
  692. public function getQ(): int { return $this->q; }
  693. public function getR(): int { return $this->r; }
  694. }
  695. }