#scripts/collision_detector.py
import cv2
import logging
import numpy as np
from pytiled_parser import parse_tileset, Tile,Size
from pytiled_parser.common_types import OrderedPair
from pytiled_parser.tiled_object import TiledObject, Polygon
from pytiled_parser.layer import ObjectLayer
import xml.etree.ElementTree as ET
import traceback
from collections import OrderedDict
from pathlib import Path
import argparse



# Configure logging
logging.basicConfig(filename='process_map.log', level=logging.DEBUG,
                    format='%(asctime)s - %(levelname)s - %(message)s')

def save_tileset(tileset, tsx_path,output):
    try:
        # TSX dosyasını aç ve kök düğümü al
        tree = ET.parse(tsx_path)
        root = tree.getroot()

        # `tiles` özelliği yoksa başlat
        if tileset.tiles is None:
            tileset.tiles = OrderedDict()

        # Her bir tile için işlem yap
        for tile_id, tile in tileset.tiles.items():
            if tile.objects:
                # Mevcut `tile` elemanını bul veya oluştur
                tile_elem = root.find(f"./tile[@id='{tile_id}']")
                if tile_elem is None:
                    tile_elem = ET.SubElement(root, 'tile', id=str(tile_id))
                else:
                    # Önceki `objectgroup` elemanlarını kaldır
                    for og in tile_elem.findall('objectgroup'):
                        tile_elem.remove(og)

                # Yeni `objectgroup` oluştur
                og_elem = ET.SubElement(tile_elem, 'objectgroup', draworder="index")
                for obj in tile.objects.tiled_objects:  # Düzeltilmiş erişim
                    obj_attribs = {
                        'id': str(obj.id),
                        'x': str(obj.coordinates.x),  # Access x from coordinates
                        'y': str(obj.coordinates.y),  # Access y from coordinates
                    }
                    if hasattr(obj, 'polygon') and obj.polygon:  # Check if the polygon attribute exists
                        # Polygon collider için işlem
                        object_elem = ET.SubElement(og_elem, 'object', obj_attribs)
                        points_str = ' '.join(['{0},{1}'.format(pt.x, pt.y) for pt in obj.polygon.points])
                        ET.SubElement(object_elem, 'polygon', points=points_str)
                    else:
                        # Rectangle collider için işlem
                        obj_attribs.update({
                            'width': str(obj.size.width),  # Access width from size
                            'height': str(obj.size.height),  # Access height from size
                        })
                        ET.SubElement(og_elem, 'object', obj_attribs)

       # Güncellenmiş TSX dosyasını kaydet
        output_file = output if (output and not Path(output).is_dir()) else tsx_path

        tree.write(output_file, encoding='utf-8', xml_declaration=True)
        logging.info(f"TSX dosyası başarıyla kaydedildi: {output_file}")

    except ET.ParseError as e:
        logging.error(f"TSX dosyası okunurken/parçalanırken hata: {e}")
    except Exception as e:
        logging.error(f"save_tileset fonksiyonunda beklenmeyen bir hata oluştu: {e}")


def process_map(image_path, tsx_path, sensitivity=100,output=None):
    """
    Processes the tileset image, detects colliders for each tile,
    and updates the TSX file with collider data.
    """
    try:
        logging.info("Starting image processing...")

        # Parse the TSX file
        tileset = parse_tileset(tsx_path)
        if not tileset:
            logging.error(f"Tileset could not be parsed: {tsx_path}")
            return

        # Ensure tiles dictionary is initialized
        if tileset.tiles is None:
            tileset.tiles = {}

        # Load the tileset image
        tileset_image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
        if tileset_image is None:
            logging.error(f"Tileset image could not be loaded: {image_path}")
            return

        tile_width = tileset.tile_width
        tile_height = tileset.tile_height

        # Calculate the number of tiles in x and y directions
        tiles_x = tileset_image.shape[1] // tile_width
        tiles_y = tileset_image.shape[0] // tile_height

        for ty in range(tiles_y):
            for tx in range(tiles_x):
                # Extract the tile image
                tile_image = tileset_image[
                    ty * tile_height : (ty + 1) * tile_height,
                    tx * tile_width : (tx + 1) * tile_width
                ]

                # Process the tile image
                edges = cv2.Canny(tile_image, threshold1=sensitivity, threshold2=sensitivity * 2)


                # Find contours in the tile
                contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

                if contours:
                    # Compute the global tile ID
                    tile_id = ty * tiles_x + tx

                    # Get or create the tile in the tileset
                    tile = tileset.tiles.get(tile_id)
                    if not tile:
                        tile = Tile(id=tile_id)
                        tileset.tiles[tile_id] = tile

                    # Create an ObjectLayer for the tile if it doesn't exist
                    logging.info(tile)
                    if tile.objects is None:
                        tile.objects = ObjectLayer(
                            name="Colliders",
                            draw_order="index",
                            tiled_objects=[]  # Corrected parameter name
                        )

                    # Add collision objects to the tile's ObjectLayer
                    for cnt in contours:
                        # Calculate contour area and tile area
                        contour_area = cv2.contourArea(cnt)
                        tile_area = tile_width * tile_height
                        area_ratio = contour_area / tile_area

                        try:
                            if area_ratio > 0.5:
                                # Create a polygon collider
                                points = cnt.reshape(-1, 2)
                                points[:, 0] = np.clip(points[:, 0], 0, tile_width)
                                points[:, 1] = np.clip(points[:, 1], 0, tile_height)
                            
                                polygon_points = [OrderedPair(x=float(pt[0]), y=float(pt[1])) for pt in points]


                                collider = TiledObject(
                                id=len(tile.objects.tiled_objects) + 1,
                                coordinates=OrderedPair(x=0, y=0),  # Coordinates (x, y)
                                size=Size(width=0, height=0),  # Size (width, height) default to 0
                                rotation=0,  # Default rotation
                                visible=True,  # Default visible
                                name="",  # Empty name
                                class_="",  # Empty class
                                properties={},  # Empty properties dictionary
                                polygon=Polygon(points=polygon_points)  # Polygon points
                                    )
                            else:
                                # Create a rectangle collider
                                x, y, w, h = cv2.boundingRect(cnt)
                                x = max(0, min(x, tile_width))
                                y = max(0, min(y, tile_height))
                                w = min(w, tile_width - x)
                                h = min(h, tile_height - y)
                                
                         # Assuming 'coordinates' is an OrderedPair (x, y), and 'size' is a Size (width, height)
                                collider = TiledObject(
                                    id=len(tile.objects.tiled_objects) + 1,  # Unique ID
                                    coordinates=OrderedPair(x=float(x), y=float(y)),  # Coordinates (x, y)
                                    size=Size(width=float(w), height=float(h)),  # Size (width, height)
                                    rotation=0,  # Default rotation
                                    visible=True,  # Default visible
                                    name="",  # Empty name
                                    class_="",  # Empty class
                                    properties={}  # Empty properties dictionary
                                )

                            
                            # Append the collider to the tile's object layer
                            tile.objects.tiled_objects.append(collider)
                        except Exception as e:
                            # Log the error along with the line number
                            logging.error(f"Error while processing tile ({tx}, {ty}): {e}")
                            logging.error(f"Error details: {traceback.format_exc()}")

        # Save the modified tileset
        save_tileset(tileset, tsx_path,output)

        logging.info("Collider settings successfully added.")

    except Exception as e:
        # Log the general error
        logging.error(f"An unexpected error occurred: {e}")
        logging.error(f"Error details: {traceback.format_exc()}")

def main():
    parser = argparse.ArgumentParser(description="Process tileset and add colliders.")
    parser.add_argument("image_path", type=str, help="Path to the tileset image")
    parser.add_argument("tsx_path", type=str, help="Path to the TSX file")
    parser.add_argument("sensitivity", type=int, help="Sensitivity value for edge detection")
    parser.add_argument("--output", type=str, default=None, help="Output directory path (optional)")

    args = parser.parse_args()

    process_map(args.image_path, args.tsx_path, args.sensitivity, args.output)

if __name__ == "__main__":
    main()
# buna elleme sakın collision_detector.py bu
______________
import numpy as np
from sklearn.preprocessing import MinMaxScaler
import logging
import traceback
from pathlib import Path
from main import MapGenerator
import xml.etree.ElementTree as ET

logging.basicConfig(level=logging.INFO)

class AIMapController:
    def __init__(self):
        self.logger = logging.getLogger(__name__)
        self.scaler = MinMaxScaler()
        
        # Parameters for different entity types
        self.params = {
            'enemy_density': 0.15,  # Enemies per tile area
            'police_density': 0.08,  # Police per tile area
            'worker_density': 0.2,   # Workers per tile area
            'trap_density': 0.1,     # Traps per tile area
            'min_distance': 100,     # Minimum distance between entities
            'view_range': {
                'enemy': 200,
                'police': 250,
                'worker': 150,
                'boss': 400
            }
        }

    def generate_map(self, map_path, difficulty=0.5):
        """Generate a complete map with AI-controlled placement"""
        try:
            self.logger.info(f"Starting AI map generation with difficulty {difficulty}")
            
            # Initialize map generator
            generator = MapGenerator(map_path)
            self.logger.info(f"Map path: {map_path}")
            # Analyze map structure
            map_data = self._analyze_map(map_path)
            
            # Adjust parameters based on difficulty
            self._adjust_parameters(difficulty)
            
            # Generate entity positions
            positions = self._generate_positions(map_data)
            
            # Apply positions to map
            self._apply_positions(generator, positions)
            
            # Generate the final map
            generator.generate()
            
            self.logger.info("AI map generation completed successfully")
            print("Map generation successful")  # Print for Node.js to capture
            return True
            
        except Exception as e:
            error_message = f"AI map generation failed: {e}\n{traceback.format_exc()}"
            self.logger.error(error_message)
            print(f"Map generation failed: {e}")
            return False

    def _analyze_map(self, map_path):
        """Analyze map structure and return useful data"""
        self.logger.info("Analyzing map...")
        try:
            # Get map dimensions and walkable areas
            map_size = self._get_map_size(map_path)
            walkable_areas = self._get_walkable_areas(map_path)

            if not walkable_areas:
                self.logger.error("No walkable areas found in the map.")
                self._log_error_to_file("No walkable areas found in the map.", map_path)
                raise ValueError("No walkable areas found in the map.")

            return {
                'size': map_size,
                'walkable_areas': walkable_areas,
                'complexity': self._calculate_complexity(walkable_areas)
            }
        except Exception as e:
            error_message = f"Map analysis failed: {e}\n{traceback.format_exc()}"
            self.logger.error(error_message)
            self._log_error_to_file(error_message, map_path)
            raise

    def _get_walkable_areas(self, map_path):
        """Get walkable areas from the collision layer"""
        try:
            tree = ET.parse(map_path)
            root = tree.getroot()

            walkable_areas = []
            width = int(root.attrib['width'])
            height = int(root.attrib['height'])
            tile_width = int(root.attrib['tilewidth'])
            tile_height = int(root.attrib['tileheight'])

            empty_layer = True

            for layer in root.findall('layer'):
                layer_name = layer.get('name')
                if layer_name == 'Tile Layer 1':
                    empty_layer = False
                    
                    data = layer.find('data').text.strip().split(',')
                    for index, tile_id in enumerate(data):
                        if int(tile_id) == 0:  # Walkable tile
                            x = (index % width) * tile_width
                            y = (index // width) * tile_height
                            walkable_areas.append({
                                'x': x,
                                'y': y,
                                'width': tile_width,
                                'height': tile_height
                            })

            # If layer is empty, consider all tiles walkable
            if empty_layer:
                for y in range(height):
                    for x in range(width):
                        walkable_areas.append({
                            'x': x * tile_width,
                            'y': y * tile_height,
                            'width': tile_width,
                            'height': tile_height
                        })

            return walkable_areas
        except Exception as e:
            error_message = f"Error getting walkable areas: {e}\n{traceback.format_exc()}"
            self.logger.error(error_message)
            self._log_error_to_file(error_message, map_path)
            return []

    def _adjust_parameters(self, difficulty):
        """Adjust entity parameters based on difficulty"""
        try:
            scale = 0.5 + difficulty  # Scale from 0.5 to 1.5
            
            self.params['enemy_density'] *= scale
            self.params['police_density'] *= scale
            self.params['trap_density'] *= scale
            
            # Adjust view ranges
            for entity_type in self.params['view_range']:
                self.params['view_range'][entity_type] = int(
                    self.params['view_range'][entity_type] * scale
                )
        except Exception as e:
            self.logger.error(f"Error adjusting parameters: {e}\n{traceback.format_exc()}")
            raise

    def _generate_positions(self, map_data):
        """Generate optimal positions for all entities"""
        try:
            positions = {
                'enemies': [],
                'police': [],
                'workers': [],
                'traps': [],
                'bosses': []
            }
            
            map_size = map_data['size']
            walkable = map_data['walkable_areas']
            
            # Calculate number of entities based on density and map size
            area = map_size[0] * map_size[1]
            
            num_enemies = int(area * self.params['enemy_density'])
            num_police = int(area * self.params['police_density'])
            num_workers = int(area * self.params['worker_density'])
            
            # Generate positions ensuring minimum distance between entities
            positions['enemies'] = self._place_entities(
                num_enemies, walkable, self.params['min_distance']
            )
            positions['police'] = self._place_entities(
                num_police, walkable, self.params['min_distance']
            )
            positions['workers'] = self._place_entities(
                num_workers, walkable, self.params['min_distance']
            )
            
            # Place boss at strategic location
            positions['bosses'] = self._place_boss(walkable, positions)
            
            return positions
        except Exception as e:
            self.logger.error(f"Error generating positions: {e}\n{traceback.format_exc()}")
            raise

    def _place_entities(self, num_entities, walkable_areas, min_distance):
        """Place entities with minimum distance constraints"""
        try:
            positions = []
            attempts = 0
            max_attempts = num_entities * 10
            
            while len(positions) < num_entities and attempts < max_attempts:
                pos = self._get_random_walkable_position(walkable_areas)
                
                # Check minimum distance from other entities
                if all(self._calculate_distance(pos, p) >= min_distance for p in positions):
                    positions.append(pos)
                
                attempts += 1
            
            return positions
        except Exception as e:
            self.logger.error(f"Error placing entities: {e}\n{traceback.format_exc()}")
            raise

    def _place_boss(self, walkable_areas, existing_positions):
        """Place boss in strategic location"""
        try:
            # Find position far from other entities but in walkable area
            best_pos = None
            max_min_distance = 0
            
            for _ in range(100):  # Try 100 random positions
                pos = self._get_random_walkable_position(walkable_areas)
                min_distance = float('inf')
                
                # Check distance from all other entities
                for positions in existing_positions.values():
                    for p in positions:
                        dist = self._calculate_distance(pos, p)
                        min_distance = min(min_distance, dist)
                
                if min_distance > max_min_distance:
                    max_min_distance = min_distance
                    best_pos = pos
            
            return [best_pos] if best_pos else []
        except Exception as e:
            self.logger.error(f"Error placing boss: {e}\n{traceback.format_exc()}")
            raise

    def _get_random_walkable_position(self, walkable_areas):
        """Get random position in walkable area"""
        try:
            area = np.random.choice(walkable_areas)
            x = np.random.uniform(area['x'], area['x'] + area['width'])
            y = np.random.uniform(area['y'], area['y'] + area['height'])
            return {'x': x, 'y': y}
        except Exception as e:
            self.logger.error(f"Error getting random walkable position: {e}\n{traceback.format_exc()}")
            raise

    def _calculate_distance(self, pos1, pos2):
        """Calculate Euclidean distance between two positions"""
        try:
            return np.sqrt(
                (pos1['x'] - pos2['x'])**2 + 
                (pos1['y'] - pos2['y'])**2
            )
        except Exception as e:
            self.logger.error(f"Error calculating distance: {e}\n{traceback.format_exc()}")
            raise

    def _apply_positions(self, generator, positions):
        """Apply generated positions to the map"""
        try:
            for entity_type, pos_list in positions.items():
                for pos in pos_list:
                    if entity_type == 'enemies':
                        generator.layers['enemies'].add_enemy(pos['x'], pos['y'])
                    elif entity_type == 'police':
                        generator.layers['police'].add_police(pos['x'], pos['y'])
                    elif entity_type == 'workers':
                        generator.layers['workers'].add_worker(pos['x'], pos['y'])
                    elif entity_type == 'bosses':
                        generator.layers['bosses'].add_boss(pos['x'], pos['y'])
        except Exception as e:
            self.logger.error(f"Error applying positions: {e}\n{traceback.format_exc()}")
            raise

    def _get_map_size(self, map_path):
        """Get map dimensions from TMX file"""
        try:
            tree = ET.parse(map_path)
            root = tree.getroot()
            return (
                int(root.get('width', 0)) * int(root.get('tilewidth', 0)),
                int(root.get('height', 0)) * int(root.get('tileheight', 0))
            )
        except Exception as e:
            self.logger.error(f"Error getting map size: {e}\n{traceback.format_exc()}")
            raise

    def _log_error_to_file(self, error_message, map_path):
        """Log error message to a file for further analysis."""
        error_log_path = 'error_log.txt'
        error_msg = f"Error in {map_path}: {error_message}\n"
        with open(error_log_path, 'a') as file:
            file.write(error_msg)
        self.logger.info(f"Error logged to {error_log_path}")

    def _calculate_complexity(self, walkable_areas):
        """Calculate map complexity score"""
        try:
            return len(walkable_areas)  # Complexity based on number of walkable areas
        except Exception as e:
            self.logger.error(f"Error calculating map complexity: {e}\n{traceback.format_exc()}")
            raise

if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(description="Generate AI maps.")
    parser.add_argument("--map", help="Path to the TMX map file", required=True)
    parser.add_argument("--difficulty", type=float, default=0.5, help="Difficulty level for map generation")
    
    args = parser.parse_args()
    
    controller = AIMapController()
    success = controller.generate_map(args.map, args.difficulty)

    if success:
        print("Map generation successful.")
    else:
        print("Map generation failed.")
_______

tüm üretimler burada klasörlere ayrılarak depolansın
output\newMaps\"map türü"\"mapidsi"
diye klasörlenlemeli

_____

import numpy as np
from sklearn.preprocessing import MinMaxScaler
import logging
import traceback
from pathlib import Path
from main import MapGenerator
import xml.etree.ElementTree as ET

logging.basicConfig(level=logging.INFO)

class AIMapController:
    def __init__(self):
        self.logger = logging.getLogger(__name__)
        self.scaler = MinMaxScaler()
        
        # Parameters for different entity types
        self.params = {
            'enemy_density': 0.15,  # Enemies per tile area
            'police_density': 0.08,  # Police per tile area
            'worker_density': 0.2,   # Workers per tile area
            'trap_density': 0.1,     # Traps per tile area
            'min_distance': 100,     # Minimum distance between entities
            'view_range': {
                'enemy': 200,
                'police': 250,
                'worker': 150,
                'boss': 400
            }
        }

    def generate_map(self, map_path, difficulty=0.5):
        """Generate a complete map with AI-controlled placement"""
        try:
            self.logger.info(f"Starting AI map generation with difficulty {difficulty}")
            
            # Initialize map generator
            generator = MapGenerator(map_path)
            self.logger.info(f"Map path: {map_path}")
            # Analyze map structure
            map_data = self._analyze_map(map_path)
            
            # Adjust parameters based on difficulty
            self._adjust_parameters(difficulty)
            
            # Generate entity positions
            positions = self._generate_positions(map_data)
            
            # Apply positions to map
            self._apply_positions(generator, positions)
            
            # Generate the final map
            generator.generate()
            
            self.logger.info("AI map generation completed successfully")
            print("Map generation successful")  # Print for Node.js to capture
            return True
            
        except Exception as e:
            error_message = f"AI map generation failed: {e}\n{traceback.format_exc()}"
            self.logger.error(error_message)
            print(f"Map generation failed: {e}")
            return False

    def _analyze_map(self, map_path):
        """Analyze map structure and return useful data"""
        self.logger.info("Analyzing map...")
        try:
            # Get map dimensions and walkable areas
            map_size = self._get_map_size(map_path)
            walkable_areas = self._get_walkable_areas(map_path)

            if not walkable_areas:
                self.logger.error("No walkable areas found in the map.")
                self._log_error_to_file("No walkable areas found in the map.", map_path)
                raise ValueError("No walkable areas found in the map.")

            return {
                'size': map_size,
                'walkable_areas': walkable_areas,
                'complexity': self._calculate_complexity(walkable_areas)
            }
        except Exception as e:
            error_message = f"Map analysis failed: {e}\n{traceback.format_exc()}"
            self.logger.error(error_message)
            self._log_error_to_file(error_message, map_path)
            raise

    def _get_walkable_areas(self, map_path):
        """Get walkable areas from the collision layer"""
        try:
            tree = ET.parse(map_path)
            root = tree.getroot()

            walkable_areas = []
            width = int(root.attrib['width'])
            height = int(root.attrib['height'])
            tile_width = int(root.attrib['tilewidth'])
            tile_height = int(root.attrib['tileheight'])

            empty_layer = True

            for layer in root.findall('layer'):
                layer_name = layer.get('name')
                if layer_name == 'Tile Layer 1':
                    empty_layer = False
                    
                    data = layer.find('data').text.strip().split(',')
                    for index, tile_id in enumerate(data):
                        if int(tile_id) == 0:  # Walkable tile
                            x = (index % width) * tile_width
                            y = (index // width) * tile_height
                            walkable_areas.append({
                                'x': x,
                                'y': y,
                                'width': tile_width,
                                'height': tile_height
                            })

            # If layer is empty, consider all tiles walkable
            if empty_layer:
                for y in range(height):
                    for x in range(width):
                        walkable_areas.append({
                            'x': x * tile_width,
                            'y': y * tile_height,
                            'width': tile_width,
                            'height': tile_height
                        })

            return walkable_areas
        except Exception as e:
            error_message = f"Error getting walkable areas: {e}\n{traceback.format_exc()}"
            self.logger.error(error_message)
            self._log_error_to_file(error_message, map_path)
            return []

    def _adjust_parameters(self, difficulty):
        """Adjust entity parameters based on difficulty"""
        try:
            scale = 0.5 + difficulty  # Scale from 0.5 to 1.5
            
            self.params['enemy_density'] *= scale
            self.params['police_density'] *= scale
            self.params['trap_density'] *= scale
            
            # Adjust view ranges
            for entity_type in self.params['view_range']:
                self.params['view_range'][entity_type] = int(
                    self.params['view_range'][entity_type] * scale
                )
        except Exception as e:
            self.logger.error(f"Error adjusting parameters: {e}\n{traceback.format_exc()}")
            raise

    def _generate_positions(self, map_data):
        """Generate optimal positions for all entities"""
        try:
            positions = {
                'enemies': [],
                'police': [],
                'workers': [],
                'traps': [],
                'bosses': []
            }
            
            map_size = map_data['size']
            walkable = map_data['walkable_areas']
            
            # Calculate number of entities based on density and map size
            area = map_size[0] * map_size[1]
            
            num_enemies = int(area * self.params['enemy_density'])
            num_police = int(area * self.params['police_density'])
            num_workers = int(area * self.params['worker_density'])
            
            # Generate positions ensuring minimum distance between entities
            positions['enemies'] = self._place_entities(
                num_enemies, walkable, self.params['min_distance']
            )
            positions['police'] = self._place_entities(
                num_police, walkable, self.params['min_distance']
            )
            positions['workers'] = self._place_entities(
                num_workers, walkable, self.params['min_distance']
            )
            
            # Place boss at strategic location
            positions['bosses'] = self._place_boss(walkable, positions)
            
            return positions
        except Exception as e:
            self.logger.error(f"Error generating positions: {e}\n{traceback.format_exc()}")
            raise

    def _place_entities(self, num_entities, walkable_areas, min_distance):
        """Place entities with minimum distance constraints"""
        try:
            positions = []
            attempts = 0
            max_attempts = num_entities * 10
            
            while len(positions) < num_entities and attempts < max_attempts:
                pos = self._get_random_walkable_position(walkable_areas)
                
                # Check minimum distance from other entities
                if all(self._calculate_distance(pos, p) >= min_distance for p in positions):
                    positions.append(pos)
                
                attempts += 1
            
            return positions
        except Exception as e:
            self.logger.error(f"Error placing entities: {e}\n{traceback.format_exc()}")
            raise

    def _place_boss(self, walkable_areas, existing_positions):
        """Place boss in strategic location"""
        try:
            # Find position far from other entities but in walkable area
            best_pos = None
            max_min_distance = 0
            
            for _ in range(100):  # Try 100 random positions
                pos = self._get_random_walkable_position(walkable_areas)
                min_distance = float('inf')
                
                # Check distance from all other entities
                for positions in existing_positions.values():
                    for p in positions:
                        dist = self._calculate_distance(pos, p)
                        min_distance = min(min_distance, dist)
                
                if min_distance > max_min_distance:
                    max_min_distance = min_distance
                    best_pos = pos
            
            return [best_pos] if best_pos else []
        except Exception as e:
            self.logger.error(f"Error placing boss: {e}\n{traceback.format_exc()}")
            raise

    def _get_random_walkable_position(self, walkable_areas):
        """Get random position in walkable area"""
        try:
            area = np.random.choice(walkable_areas)
            x = np.random.uniform(area['x'], area['x'] + area['width'])
            y = np.random.uniform(area['y'], area['y'] + area['height'])
            return {'x': x, 'y': y}
        except Exception as e:
            self.logger.error(f"Error getting random walkable position: {e}\n{traceback.format_exc()}")
            raise

    def _calculate_distance(self, pos1, pos2):
        """Calculate Euclidean distance between two positions"""
        try:
            return np.sqrt(
                (pos1['x'] - pos2['x'])**2 + 
                (pos1['y'] - pos2['y'])**2
            )
        except Exception as e:
            self.logger.error(f"Error calculating distance: {e}\n{traceback.format_exc()}")
            raise

    def _apply_positions(self, generator, positions):
        """Apply generated positions to the map"""
        try:
            for entity_type, pos_list in positions.items():
                for pos in pos_list:
                    if entity_type == 'enemies':
                        generator.layers['enemies'].add_enemy(pos['x'], pos['y'])
                    elif entity_type == 'police':
                        generator.layers['police'].add_police(pos['x'], pos['y'])
                    elif entity_type == 'workers':
                        generator.layers['workers'].add_worker(pos['x'], pos['y'])
                    elif entity_type == 'bosses':
                        generator.layers['bosses'].add_boss(pos['x'], pos['y'])
        except Exception as e:
            self.logger.error(f"Error applying positions: {e}\n{traceback.format_exc()}")
            raise

    def _get_map_size(self, map_path):
        """Get map dimensions from TMX file"""
        try:
            tree = ET.parse(map_path)
            root = tree.getroot()
            return (
                int(root.get('width', 0)) * int(root.get('tilewidth', 0)),
                int(root.get('height', 0)) * int(root.get('tileheight', 0))
            )
        except Exception as e:
            self.logger.error(f"Error getting map size: {e}\n{traceback.format_exc()}")
            raise

    def _log_error_to_file(self, error_message, map_path):
        """Log error message to a file for further analysis."""
        error_log_path = 'error_log.txt'
        error_msg = f"Error in {map_path}: {error_message}\n"
        with open(error_log_path, 'a') as file:
            file.write(error_msg)
        self.logger.info(f"Error logged to {error_log_path}")

    def _calculate_complexity(self, walkable_areas):
        """Calculate map complexity score"""
        try:
            return len(walkable_areas)  # Complexity based on number of walkable areas
        except Exception as e:
            self.logger.error(f"Error calculating map complexity: {e}\n{traceback.format_exc()}")
            raise

if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(description="Generate AI maps.")
    parser.add_argument("--map", help="Path to the TMX map file", required=True)
    parser.add_argument("--difficulty", type=float, default=0.5, help="Difficulty level for map generation")
    
    args = parser.parse_args()
    
    controller = AIMapController()
    success = controller.generate_map(args.map, args.difficulty)

    if success:
        print("Map generation successful.")
    else:
        print("Map generation failed.")

_____

#main.py
import os
import traceback
from core.map_loader import MapLoader
from core.object_handler import ObjectHandler
from layers.bg_layer import BackgroundLayer
from layers.stories_layer import StoriesLayer
from layers.important_coords import ImportantCoordsLayer
from layers.ladders_layer import LaddersLayer
from layers.dangerous_layer import DangerousLayer
from layers.mission_layer import MissionLayer
from layers.texts_layer import TextsLayer
from entities.enemy_placer import EnemyPlacer
from entities.police_placer import PolicePlacer
from entities.worker_placer import WorkerPlacer
from entities.mover_placer import MoverPlacer
from entities.boss_placer import BossPlacer

class MapGenerator:
    def __init__(self, map_path):
        self.map_loader = MapLoader(map_path)
        self.object_handler = ObjectHandler()
        self.layers = {
            'background': BackgroundLayer(),
            'stories': StoriesLayer(),
            'important_coords': ImportantCoordsLayer(),
            'ladders': LaddersLayer(),
            'dangerous': DangerousLayer(),
            'mission': MissionLayer(),
            'texts': TextsLayer(),
            'enemies': EnemyPlacer(),
            'police': PolicePlacer(),
            'workers': WorkerPlacer(),
            'movers': MoverPlacer(),
            'bosses': BossPlacer()
        }

    def generate(self):
        """Generate the complete map with all layers and entities"""
        try:
            map_data = self.map_loader.load()
            if map_data is None:
                print("Failed to load map data.")
                return

            # Process each layer
            for layer_name, layer in self.layers.items():
                try:
                    layer.process(map_data)
                    print(f"Processed {layer_name} layer successfully.")
                except Exception as e:
                    print(f"Error processing {layer_name} layer: {e}")
                    traceback.print_exc()  # Print detailed traceback for debugging

            self.map_loader.save(map_data)
            print("Map generation completed successfully.")
        
        except Exception as main_exception:
            print(f"Failed to generate the map: {main_exception}")
            traceback.print_exc()

if __name__ == "__main__":
    import argparse
    
    parser = argparse.ArgumentParser(description="Generate game map with all layers and entities")
    parser.add_argument("map_path", help="Path to the TMX map file")
    
    args = parser.parse_args()
    
    generator = MapGenerator(args.map_path)
    generator.generate()

_______

//index.js
const { PythonShell } = require('python-shell');
const path = require('path');
const fs = require('fs');

// Function to process the tileset image and update the TSX file
function processTilesetImage(imagePath, tsxPath, sensitivity = 100, output = null) {
    return new Promise((resolve, reject) => {
        console.log(`Processing tileset image: ${imagePath}`);

        const options = {
            mode: 'text',
            pythonPath: 'python',
            pythonOptions: ['-u'], // unbuffered output
            scriptPath: __dirname,
            args: [imagePath, tsxPath, sensitivity.toString()]
        };

        // Add output to arguments if provided
        if (output) {
            options.args.push('--output', output);
        }

        PythonShell.run('collision_detector.py', options, (err, results) => {
            if (err) {
                console.error('Error processing tileset image:', err);
                reject(err);
            } else {
                console.log('Tileset image processed successfully');
                resolve(results);
            }
        });
    });
}

// Function to create the base TMX map
function tileBaseMapForGeneration(mapType, width, height, perTileSize, outputMapPath) {
    return new Promise((resolve, reject) => {
        console.log(`Creating base map with type: ${mapType}, width: ${width}, height: ${height}, tileSize: ${perTileSize.x}x${perTileSize.y}`);

        const options = {
            mode: 'text',
            pythonPath: 'python',
            pythonOptions: ['-u'],
            scriptPath: __dirname,
            args: [
                '--map_type', mapType,
                '--width', width.toString(),
                '--height', height.toString(),
                '--tile_width', perTileSize.x.toString(),
                '--tile_height', perTileSize.y.toString(),
                '--output', outputMapPath // Output path is retained
            ]
        };

        PythonShell.run('create_base_map.py', options, (err, results) => {
            if (err) {
                console.error('Error creating base map:', err);
                reject(err);
            } else {
                console.log('Base map created successfully');
                resolve(results);
            }
        });
    });
}

// Map generation function
function generateMap(mapPath, difficulty = 0.5, mapType = 'platform', mapId = 'level1') {
    return new Promise((resolve, reject) => {
        console.log(`Starting map generation with path: ${mapPath}, difficulty: ${difficulty}, map type: ${mapType}, map id: ${mapId}`);

        const options = {
            mode: 'text',
            pythonPath: 'python',
            pythonOptions: ['-u'],
            scriptPath: __dirname,
            args: [
                '--map', mapPath,
                '--difficulty', difficulty.toString(),
                '--map_type', mapType,
                '--map_id', mapId
            ]
        };
        

        PythonShell.run('ai_controller.py', options, (err, results) => {
            if (err) {
                console.error('Error running AI controller:', err);
                reject(err);
            } else {
                console.log('Python script executed successfully');
                if (results && results.length > 0) {
                    console.log('Map generation completed:');
                    results.forEach(result => console.log(result));
                    resolve(results);
                } else {
                    console.error('No results returned or unexpected output.');
                    reject(new Error('No results returned.'));
                }
            }
        });
    });
}

// Main function
async function main() {
    try {
        // Paths to your tileset image and TSX file
        const imagePath = path.join(__dirname, 'maps', 'boş', 'fabrika (2).png');
        const tsxPath = path.join(__dirname, 'maps', 'boş', 'tsx', 'fabrika.tmx');
        const outputTsxPath = path.join(__dirname, 'output', 'collisionData.tmx'); // Output path for processed TSX file

        // Process the tileset image to add colliders
        await processTilesetImage(imagePath, tsxPath, 100, outputTsxPath);

        // Map parameters
        const mapType = 'platform'; // or 'top-down', 'isometric'
        const width = 100; // example width
        const height = 50; // example height
        const perTileSize = { x: 32, y: 32 }; // example tile size

        // Output map path
        const baseMapDir = path.join(__dirname, 'output', 'newMaps', mapType, 'baseMaps');
        const outputMapPath = path.join(baseMapDir, 'level1.tmx');

        // Ensure base map directory exists
        fs.mkdirSync(baseMapDir, { recursive: true });

        // Create the base map
        await tileBaseMapForGeneration(mapType, width, height, perTileSize, outputMapPath);

        // Check if the base map was created
        if (!fs.existsSync(outputMapPath)) {
            console.error(`Base map file does not exist: ${outputMapPath}`);
            return;
        }

        console.log('Starting map generation...');
        // Generate the map using the base map
        const mapId = 'level1'; // You can generate or specify map IDs as needed
        await generateMap(outputMapPath, 0.7, mapType, mapId);

        console.log('Map generation completed successfully');
    } catch (error) {
        console.error('Map generation failed:', error);
        process.exit(1);
    }
}

// Run the main function if this file is executed directly
if (require.main === module) {
    main();
}


//desen setide kullansın çünkü desen seti de var elimizde
____

import xml.etree.ElementTree as ET

class BackgroundLayer:
    def __init__(self):
        self.layer_name = "bg_layer"
    
    def process(self, map_data):
        """Process background layer objects without physics"""
        layer = map_data.find(f".//layer[@name='{self.layer_name}']")
        if layer is None:
            layer = ET.SubElement(map_data.getroot(), 'layer')
            layer.set('name', self.layer_name)
            
        # Set layer properties for non-physical objects
        properties = ET.SubElement(layer, 'properties')
        physics_prop = ET.SubElement(properties, 'property')
        physics_prop.set('name', 'hasPhysics')
        physics_prop.set('value', 'false')_

____
import xml.etree.ElementTree as ET

class DangerousLayer:
    def __init__(self):
        self.layer_name = "dangerous"
    
    def process(self, map_data):
        """Process dangerous objects like TNT and traps"""
        obj_group = map_data.find(f".//objectgroup[@name='{self.layer_name}']")
        if obj_group is None:
            obj_group = ET.SubElement(map_data.getroot(), 'objectgroup')
            obj_group.set('name', self.layer_name)
        
        for danger in obj_group.findall(".//object"):
            danger_type = danger.get('type', 'trap')
            if danger_type == 'tnt':
                self._add_tnt_properties(danger)
            else:
                self._add_trap_properties(danger)
    
    def _add_tnt_properties(self, tnt):
        properties = tnt.find('properties')
        if properties is None:
            properties = ET.SubElement(tnt, 'properties')
        
        # Add explosion properties
        explosion_prop = ET.SubElement(properties, 'property')
        explosion_prop.set('name', 'explosion_radius')
        explosion_prop.set('value', '100')
        
        damage_prop = ET.SubElement(properties, 'property')
        damage_prop.set('name', 'damage')
        damage_prop.set('value', '100')
    
    def _add_trap_properties(self, trap):
        properties = trap.find('properties')
        if properties is None:
            properties = ET.SubElement(trap, 'properties')
        
        # Add trap properties
        hidden_prop = ET.SubElement(properties, 'property')
        hidden_prop.set('name', 'hidden')
        hidden_prop.set('value', 'true')
        
        damage_prop = ET.SubElement(properties, 'property')
        damage_prop.set('name', 'damage')
        damage_prop.set('value', '50')
____
import xml.etree.ElementTree as ET

class ImportantCoordsLayer:
    def __init__(self):
        self.layer_name = "important_coords"
    
    def process(self, map_data):
        """Process important coordinates (death line, checkpoints, goals)"""
        obj_group = map_data.find(f".//objectgroup[@name='{self.layer_name}']")
        if obj_group is None:
            obj_group = ET.SubElement(map_data.getroot(), 'objectgroup')
            obj_group.set('name', self.layer_name)
        
        # Process each important point
        for point in obj_group.findall(".//object"):
            point_type = point.get('type', '')
            properties = point.find('properties')
            if properties is None:
                properties = ET.SubElement(point, 'properties')
            
            if point_type == 'death_line':
                self._add_death_line_properties(properties)
            elif point_type == 'checkpoint':
                self._add_checkpoint_properties(properties)
            elif point_type == 'goal':
                self._add_goal_properties(properties)
    
    def _add_death_line_properties(self, properties):
        death_prop = ET.SubElement(properties, 'property')
        death_prop.set('name', 'kills_player')
        death_prop.set('value', 'true')
    
    def _add_checkpoint_properties(self, properties):
        save_prop = ET.SubElement(properties, 'property')
        save_prop.set('name', 'is_checkpoint')
        save_prop.set('value', 'true')
    
    def _add_goal_properties(self, properties):
        goal_prop = ET.SubElement(properties, 'property')
        goal_prop.set('name', 'is_goal')
        goal_prop.set('value', 'true')
____
import xml.etree.ElementTree as ET

class LaddersLayer:
    def __init__(self):
        self.layer_name = "ladders"
    
    def process(self, map_data):
        """Process ladder objects in the map"""
        obj_group = map_data.find(f".//objectgroup[@name='{self.layer_name}']")
        if obj_group is None:
            obj_group = ET.SubElement(map_data.getroot(), 'objectgroup')
            obj_group.set('name', self.layer_name)
        
        for ladder in obj_group.findall(".//object"):
            self._add_ladder_properties(ladder)
    
    def _add_ladder_properties(self, ladder):
        properties = ladder.find('properties')
        if properties is None:
            properties = ET.SubElement(ladder, 'properties')
        
        # Add climbing property
        climb_prop = ET.SubElement(properties, 'property')
        climb_prop.set('name', 'climbable')
        climb_prop.set('value', 'true')
        
        # Add interaction area
        interact_prop = ET.SubElement(properties, 'property')
        interact_prop.set('name', 'interaction_area')
        interact_prop.set('value', 'true')
____
import xml.etree.ElementTree as ET

class MissionLayer:
    def __init__(self):
        self.layer_name = "mission"
    
    def process(self, map_data):
        """Process mission objects like keys and objectives"""
        obj_group = map_data.find(f".//objectgroup[@name='{self.layer_name}']")
        if obj_group is None:
            obj_group = ET.SubElement(map_data.getroot(), 'objectgroup')
            obj_group.set('name', self.layer_name)
        
        for mission_obj in obj_group.findall(".//object"):
            obj_type = mission_obj.get('type', 'key')
            if obj_type == 'key':
                self._add_key_properties(mission_obj)
            else:
                self._add_objective_properties(mission_obj)
    
    def _add_key_properties(self, key):
        properties = key.find('properties')
        if properties is None:
            properties = ET.SubElement(key, 'properties')
        
        # Add key properties
        collect_prop = ET.SubElement(properties, 'property')
        collect_prop.set('name', 'collectable')
        collect_prop.set('value', 'true')
        
        key_id_prop = ET.SubElement(properties, 'property')
        key_id_prop.set('name', 'key_id')
        key_id_prop.set('value', key.get('id', '1'))
    
    def _add_objective_properties(self, objective):
        properties = objective.find('properties')
        if properties is None:
            properties = ET.SubElement(objective, 'properties')
        
        # Add objective properties
        interact_prop = ET.SubElement(properties, 'property')
        interact_prop.set('name', 'interactive')
        interact_prop.set('value', 'true')
        
        required_key_prop = ET.SubElement(properties, 'property')
        required_key_prop.set('name', 'required_key_id')
        required_key_prop.set('value', '1')
____
import xml.etree.ElementTree as ET

class StoriesLayer:
    def __init__(self):
        self.layer_name = "stories"
    
    def process(self, map_data):
        """Process interactive story points"""
        obj_group = map_data.find(f".//objectgroup[@name='{self.layer_name}']")
        if obj_group is None:
            obj_group = ET.SubElement(map_data.getroot(), 'objectgroup')
            obj_group.set('name', self.layer_name)
        
        # Add story point properties
        for story_point in obj_group.findall(".//object"):
            properties = story_point.find('properties')
            if properties is None:
                properties = ET.SubElement(story_point, 'properties')
            
            # Add interaction property
            interact_prop = ET.SubElement(properties, 'property')
            interact_prop.set('name', 'interactive')
            interact_prop.set('value', 'true')
____
import xml.etree.ElementTree as ET

class TextsLayer:
    def __init__(self):
        self.layer_name = "texts"
    
    def process(self, map_data):
        """Process text objects with glowing effects"""
        obj_group = map_data.find(f".//objectgroup[@name='{self.layer_name}']")
        if obj_group is None:
            obj_group = ET.SubElement(map_data.getroot(), 'objectgroup')
            obj_group.set('name', self.layer_name)
        
        for text_obj in obj_group.findall(".//object"):
            self._add_text_properties(text_obj)
    
    def _add_text_properties(self, text_obj):
        properties = text_obj.find('properties')
        if properties is None:
            properties = ET.SubElement(text_obj, 'properties')
        
        # Add glowing effect property
        glow_prop = ET.SubElement(properties, 'property')
        glow_prop.set('name', 'glowing')
        glow_prop.set('value', '1')
        
        # Add text type property
        type_prop = ET.SubElement(properties, 'property')
        type_prop.set('name', 'type')
        type_prop.set('value', '1')
____
kullanıcı isterse farklı map türleri üretebilmeli bu ai da
izometik
platform
top-down

gibi...
____
bu scriptler modüler çalışacak
hiç bir özellik azalamamalı tüm özellikler korunmalı ve bu şekilde proje bitmeli o yüzden kdu tamamla

yeni özellik = yeni modüler dosya demek


hatasız iş bekliyorum