""" Point Mapping System for PyCatan This module provides translation between user-friendly point IDs (1, 2, 3...) and internal coordinate system ([row, index]). This creates a unified point reference system for both human input and visualization. """ from typing import Dict, List, Tuple, Optional import json import os class PointMapper: """ Manages mapping between point IDs and coordinates. Point IDs are simple numbers (1, 2, 3...) that users can easily reference. Coordinates are [row, index] pairs used internally by the game engine. """ def __init__(self): """Initialize the point mapper.""" self.point_to_coords: Dict[int, List[int]] = {} self.coords_to_point: Dict[str, int] = {} self._load_default_mapping() def _load_default_mapping(self): """Load the default Catan board point mapping.""" # Standard Catan board layout - 54 intersection points # This follows the hexagonal board structure with 19 tiles default_mapping = [ # Top row (7 points) - wider at the top [0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [0, 6], # Second row (9 points) [1, 0], [1, 1], [1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [1, 7], [1, 8], # Third row (11 points) - widest row [2, 0], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5], [2, 6], [2, 7], [2, 8], [2, 9], [2, 10], # Fourth row (11 points) - also widest [3, 0], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [3, 6], [3, 7], [3, 8], [3, 9], [3, 10], # Fifth row (9 points) [4, 0], [4, 1], [4, 2], [4, 3], [4, 4], [4, 5], [4, 6], [4, 7], [4, 8], # Bottom row (7 points) - narrows at the bottom [5, 0], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5], [5, 6] ] # Create both mappings for point_id, coords in enumerate(default_mapping, 1): self.point_to_coords[point_id] = coords self.coords_to_point[f"{coords[0]},{coords[1]}"] = point_id def point_to_coordinate(self, point_id: int) -> Optional[List[int]]: """Convert point ID to coordinates.""" return self.point_to_coords.get(point_id) def coordinate_to_point(self, row: int, index: int) -> Optional[int]: """Convert coordinates to point ID.""" return self.coords_to_point.get(f"{row},{index}") def get_all_points(self) -> List[int]: """Get all valid point IDs.""" return sorted(self.point_to_coords.keys()) def get_adjacent_points(self, point_id: int) -> List[int]: """ Get points adjacent to the given point (for road validation). This is a simplified version - in a real implementation, this would check actual board topology. """ coords = self.point_to_coordinate(point_id) if not coords: return [] row, index = coords adjacent_coords = [ [row-1, index-1], [row-1, index], [row-1, index+1], [row, index-1], [row, index+1], [row+1, index-1], [row+1, index], [row+1, index+1] ] adjacent_points = [] for adj_coords in adjacent_coords: adj_point = self.coordinate_to_point(adj_coords[0], adj_coords[1]) if adj_point: adjacent_points.append(adj_point) return adjacent_points def validate_road_placement(self, start_point: int, end_point: int) -> bool: """Check if two points can be connected by a road.""" if start_point == end_point: return False # Check if points are adjacent adjacent_to_start = self.get_adjacent_points(start_point) return end_point in adjacent_to_start def export_mapping(self, filename: str = "point_mapping.json"): """Export mapping to JSON file for use by visualizations.""" export_data = { "point_to_coords": self.point_to_coords, "coords_to_point": self.coords_to_point, "total_points": len(self.point_to_coords) } with open(filename, 'w') as f: json.dump(export_data, f, indent=2) print(f"Point mapping exported to {filename}") def import_mapping(self, filename: str): """Import mapping from JSON file.""" if not os.path.exists(filename): print(f"Mapping file {filename} not found, using default mapping") return with open(filename, 'r') as f: data = json.load(f) # Convert string keys back to integers for point_to_coords self.point_to_coords = {int(k): v for k, v in data["point_to_coords"].items()} self.coords_to_point = data["coords_to_point"] print(f"Point mapping imported from {filename}") def print_mapping(self): """Print the current mapping for debugging.""" print("Point ID -> Coordinates mapping:") print("=" * 40) for point_id in sorted(self.point_to_coords.keys()): coords = self.point_to_coords[point_id] print(f"Point {point_id:2d} -> [{coords[0]}, {coords[1]}]") print(f"\\nTotal points: {len(self.point_to_coords)}") # Global point mapper instance point_mapper = PointMapper() # Convenience functions for easy import def point_to_coords(point_id: int) -> Optional[List[int]]: """Convert point ID to coordinates.""" return point_mapper.point_to_coordinate(point_id) def coords_to_point(row: int, index: int) -> Optional[int]: """Convert coordinates to point ID.""" return point_mapper.coordinate_to_point(row, index) def validate_road(start_point: int, end_point: int) -> bool: """Check if a road can be placed between two points.""" return point_mapper.validate_road_placement(start_point, end_point) def get_all_points() -> List[int]: """Get all valid point IDs.""" return point_mapper.get_all_points() if __name__ == "__main__": # Demo and testing print("PyCatan Point Mapping System") print("=" * 40) # Print the mapping point_mapper.print_mapping() # Test some conversions print("\\n" + "=" * 40) print("Testing conversions:") test_points = [1, 10, 25, 54] for point in test_points: coords = point_to_coords(point) if coords: back_to_point = coords_to_point(coords[0], coords[1]) print(f"Point {point} -> {coords} -> Point {back_to_point}") # Test road validation print("\\n" + "=" * 40) print("Testing road placements:") test_roads = [(1, 2), (1, 10), (25, 26), (1, 54)] for start, end in test_roads: valid = validate_road(start, end) status = "✓" if valid else "✗" print(f"Road {start} -> {end}: {status}") # Export mapping for web visualization print("\\n" + "=" * 40) point_mapper.export_mapping("pycatan/static/js/point_mapping.json")