| | """ |
| | map_graph.py |
| | Gère la structure du graphe, la persistance JSON et les algorithmes spatiaux (BFS). |
| | """ |
| | import json |
| | import os |
| | from dataclasses import dataclass, field, asdict |
| | from typing import Dict, List, Optional, Set, Tuple |
| | from collections import deque |
| |
|
| | @dataclass |
| | class Connection: |
| | to_id: int |
| | direction: str |
| | is_verified: bool = True |
| |
|
| | @dataclass |
| | class RoomNode: |
| | id: int |
| | name: str |
| | |
| | exits: Dict[str, Connection] = field(default_factory=dict) |
| | |
| | failed_exits: Dict[str, int] = field(default_factory=dict) |
| | visited_count: int = 1 |
| |
|
| | class MapGraph: |
| | def __init__(self): |
| | self.rooms: Dict[int, RoomNode] = {} |
| | |
| | |
| | self.inverse_dirs = { |
| | "n": "s", "s": "n", "e": "w", "w": "e", |
| | "north": "south", "south": "north", |
| | "east": "west", "west": "east", |
| | "up": "down", "down": "up", |
| | "ne": "sw", "sw": "ne", "nw": "se", "se": "nw", |
| | "enter": "exit", "exit": "enter", "in": "out", "out": "in" |
| | } |
| |
|
| | def add_or_update_room(self, room_id: int, name: str): |
| | """Crée ou met à jour un noeud.""" |
| | if room_id not in self.rooms: |
| | self.rooms[room_id] = RoomNode(id=room_id, name=name) |
| | else: |
| | self.rooms[room_id].visited_count += 1 |
| | |
| | if len(name) > len(self.rooms[room_id].name): |
| | self.rooms[room_id].name = name |
| |
|
| | def add_connection(self, from_id: int, to_id: int, direction: str): |
| | """Ajoute une arête dirigée.""" |
| | if from_id not in self.rooms or to_id not in self.rooms: |
| | return |
| |
|
| | |
| | self.rooms[from_id].exits[direction] = Connection(to_id=to_id, direction=direction, is_verified=True) |
| | |
| | |
| | if direction in self.rooms[from_id].failed_exits: |
| | del self.rooms[from_id].failed_exits[direction] |
| |
|
| | |
| | |
| | rev_dir = self.inverse_dirs.get(direction) |
| | if rev_dir: |
| | target_room = self.rooms[to_id] |
| | |
| | if rev_dir not in target_room.exits: |
| | target_room.exits[rev_dir] = Connection(to_id=from_id, direction=rev_dir, is_verified=False) |
| |
|
| | def record_failure(self, room_id: int, direction: str): |
| | """Enregistre un mur/bloquage.""" |
| | if room_id in self.rooms: |
| | self.rooms[room_id].failed_exits[direction] = self.rooms[room_id].failed_exits.get(direction, 0) + 1 |
| |
|
| | def get_shortest_path(self, start_id: int, target_id: int) -> Optional[List[str]]: |
| | """BFS pour trouver le chemin le plus court.""" |
| | queue = deque([(start_id, [])]) |
| | visited = {start_id} |
| | |
| | while queue: |
| | curr, path = queue.popleft() |
| | if curr == target_id: |
| | return path |
| | |
| | if curr in self.rooms: |
| | for d, conn in self.rooms[curr].exits.items(): |
| | if conn.to_id not in visited: |
| | visited.add(conn.to_id) |
| | queue.append((conn.to_id, path + [d])) |
| | return None |
| |
|
| | def get_hubs(self) -> List[str]: |
| | """Retourne les noms des salles importantes (Hubs) avec >3 sorties.""" |
| | return [r.name for r in self.rooms.values() if len(r.exits) > 3] |
| |
|
| | |
| | def save(self, filepath: str): |
| | data = { |
| | str(rid): asdict(node) for rid, node in self.rooms.items() |
| | } |
| | with open(filepath, 'w') as f: |
| | json.dump(data, f, indent=2) |
| |
|
| | def load(self, filepath: str): |
| | if not os.path.exists(filepath): return |
| | try: |
| | with open(filepath, 'r') as f: |
| | data = json.load(f) |
| | self.rooms = {} |
| | for rid, rdata in data.items(): |
| | node = RoomNode(id=int(rid), name=rdata['name'], visited_count=rdata['visited_count']) |
| | node.failed_exits = rdata['failed_exits'] |
| | |
| | node.exits = {d: Connection(**c) for d, c in rdata['exits'].items()} |
| | self.rooms[int(rid)] = node |
| | except Exception as e: |
| | print(f"Error loading map: {e}") |