import tkinter as tk from tkinter import ttk import numpy as np import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from generation_landwater import WorldGenerator from generation_heightmap import HeightmapGenerator from climate_zones_map import ClimateGenerator from settings import WorldSettings from map_styler import MapStyler import threading import tempfile import os from PIL import Image, ImageTk class WorldGeneratorApp: def __init__(self, root): self.root = root self.settings = WorldSettings() self._init_variables() self._setup_ui() self.root.protocol("WM_DELETE_WINDOW", self._on_close) def _init_variables(self): """Инициализация переменных""" self.width_var = tk.IntVar(value=self.settings.width) self.height_var = tk.IntVar(value=self.settings.height) self.mode_var = tk.StringVar(value=self.settings.mode) # Параметры рельефа self.terrain_rough_var = tk.DoubleVar(value=self.settings.terrain_roughness) self.continent_size_var = tk.DoubleVar(value=self.settings.continent_size) self.continent_rough_var = tk.DoubleVar(value=self.settings.continent_roughness) self.islands_dens_var = tk.DoubleVar(value=self.settings.islands_density) self.islands_rough_var = tk.DoubleVar(value=self.settings.islands_roughness) self.island_size_var = tk.DoubleVar(value=self.settings.island_size) # Биомы self.biome_vars = { 'ice': tk.BooleanVar(value=True), 'tundra': tk.BooleanVar(value=True), 'taiga': tk.BooleanVar(value=True), 'forest': tk.BooleanVar(value=True), 'desert': tk.BooleanVar(value=True), 'steppe': tk.BooleanVar(value=True), 'swamp': tk.BooleanVar(value=True), 'jungle': tk.BooleanVar(value=True), 'savanna': tk.BooleanVar(value=True) } # Данные карт self.height_map = None self.biome_map = None self.map_window = None self.canvas = None def _setup_ui(self): """Настройка интерфейса""" self.root.title("Генератор мира") self.root.geometry("900x650") main_frame = ttk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # Панель управления control_frame = ttk.LabelFrame(main_frame, text="Управление") control_frame.pack(fill=tk.X, pady=5) # Первый ряд кнопок btn_row1 = ttk.Frame(control_frame) btn_row1.pack(fill=tk.X) buttons_row1 = [ ("Сгенерировать землю", self.generate_land), ("Сгенерировать высоты", self.generate_height), ("Сгенерировать климат", self.generate_climate), ("Показать карту", self.show_map) ] for i, (text, cmd) in enumerate(buttons_row1): ttk.Button(btn_row1, text=text, command=cmd).grid( row=0, column=i, padx=5, pady=5, sticky="ew") btn_row1.grid_columnconfigure(i, weight=1) # Второй ряд кнопок btn_row2 = ttk.Frame(control_frame) btn_row2.pack(fill=tk.X, pady=5) buttons_row2 = [ ("АВТО", self.auto_generate), ("Стилизовать под свиток", self.style_map), ("Настройки", self.open_settings) ] for i, (text, cmd) in enumerate(buttons_row2): ttk.Button(btn_row2, text=text, command=cmd).grid( row=0, column=i, padx=5, pady=5, sticky="ew") btn_row2.grid_columnconfigure(i, weight=1) # Статус бар self.status_var = tk.StringVar(value="Готов к работе") ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W).pack(fill=tk.X, pady=5) def _on_close(self): """Корректное закрытие приложения""" if self.map_window: self.map_window.destroy() plt.close('all') self.root.destroy() def auto_generate(self): """Автоматическая генерация без стилизации""" def _generate(): try: self.status_var.set("Автогенерация: создание земли...") self.generate_land() self.status_var.set("Автогенерация: создание высот...") self.generate_height() self.status_var.set("Автогенерация: создание климата...") self.generate_climate() self.status_var.set("Автогенерация завершена!") self.show_map() except Exception as e: self.status_var.set(f"Ошибка: {str(e)}") threading.Thread(target=_generate, daemon=True).start() def open_settings(self): settings_win = tk.Toplevel(self.root) settings_win.title("Настройки генерации") settings_win.geometry("600x500") notebook = ttk.Notebook(settings_win) # Основные настройки basic_frame = ttk.Frame(notebook) self.create_basic_settings(basic_frame) notebook.add(basic_frame, text="Основные") # Настройки биомов biome_frame = ttk.Frame(notebook) self.create_biome_settings(biome_frame) notebook.add(biome_frame, text="Биомы") notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # Кнопки управления btn_frame = ttk.Frame(settings_win) ttk.Button(btn_frame, text="Применить", command=lambda: [self.save_settings(), settings_win.destroy()]).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="Отмена", command=settings_win.destroy).pack(side=tk.LEFT, padx=5) btn_frame.pack(pady=10) def style_map(self): """Стилизация текущей карты под старинный свиток""" if not hasattr(self, 'biome_map') and not hasattr(self, 'height_map'): self.status_var.set("Ошибка: нет данных для стилизации") return try: self.status_var.set("Стилизация...") self.root.update_idletasks() # Подготовка данных if hasattr(self, 'biome_map') and self.biome_map is not None: img_data = (self.biome_map[:, :, :3] * 255).astype(np.uint8) else: norm_height = (self.height_map - self.height_map.min()) / (self.height_map.max() - self.height_map.min()) img_data = (plt.cm.terrain(norm_height)[:, :, :3] * 255).astype(np.uint8) # Создаем PIL Image и применяем стилизацию pil_img = Image.fromarray(img_data, 'RGB') styled_img = MapStyler.apply_parchment_effect(pil_img) # Отображаем результат self.preview_window = tk.Toplevel(self.root) self.preview_window.title("Стилизованная карта") img_tk = ImageTk.PhotoImage(styled_img) label = ttk.Label(self.preview_window, image=img_tk) label.image = img_tk label.pack() self.status_var.set("Стилизация завершена!") except Exception as e: self.status_var.set(f"Ошибка стилизации: {str(e)}") def create_basic_settings(self, frame): """Настройки размера и типа карты""" # Размер карты size_frame = ttk.LabelFrame(frame, text="Размер карты") size_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Label(size_frame, text="Ширина:").grid(row=0, column=0, sticky="e") ttk.Spinbox(size_frame, from_=100, to=1000, textvariable=self.width_var).grid( row=0, column=1, sticky="ew", padx=5) ttk.Label(size_frame, text="Высота:").grid(row=1, column=0, sticky="e") ttk.Spinbox(size_frame, from_=100, to=1000, textvariable=self.height_var).grid( row=1, column=1, sticky="ew", padx=5) # Тип генерации type_frame = ttk.LabelFrame(frame, text="Тип ландшафта") type_frame.pack(fill=tk.X, padx=5, pady=5) types = ["land_only", "continent", "islands"] texts = ["Только суша", "Материк", "Острова"] for t, text in zip(types, texts): ttk.Radiobutton(type_frame, text=text, variable=self.mode_var, value=t).pack( anchor=tk.W) # Параметры рельефа terrain_frame = ttk.LabelFrame(frame, text="Параметры рельефа") terrain_frame.pack(fill=tk.X, padx=5, pady=5) params = [ ("Шероховатость:", self.terrain_rough_var), ("Размер материка:", self.continent_size_var), ("Шероховатость материка:", self.continent_rough_var), ("Плотность островов:", self.islands_dens_var), ("Шероховатость островов:", self.islands_rough_var), ("Размер островов:", self.island_size_var) ] for i, (text, var) in enumerate(params): ttk.Label(terrain_frame, text=text).grid(row=i, column=0, sticky="e", padx=5) ttk.Scale(terrain_frame, from_=0.1, to=1.0, variable=var, orient=tk.HORIZONTAL).grid(row=i, column=1, sticky="ew", padx=5) ttk.Label(terrain_frame, textvariable=var).grid(row=i, column=2, padx=5) # Режим климата climate_frame = ttk.LabelFrame(frame, text="Режим климата") climate_frame.pack(fill=tk.X, padx=5, pady=5) if not hasattr(self.settings, 'climate_mode'): self.settings.climate_mode = 'realistic' ttk.Radiobutton(climate_frame, text="Реалистичный", variable=self.settings.climate_mode, value="realistic").pack(anchor=tk.W) ttk.Radiobutton(climate_frame, text="Художественный", variable=self.settings.climate_mode, value="artistic").pack(anchor=tk.W) def create_biome_settings(self, frame): """Настройки биомов через чекбоксы""" biome_frame = ttk.LabelFrame(frame, text="Выбор биомов") biome_frame.pack(fill=tk.BOTH, padx=5, pady=5, expand=True) for i, (biome, var) in enumerate(self.biome_vars.items()): col = i % 3 row = i // 3 ttk.Checkbutton( biome_frame, text=biome.capitalize(), variable=var ).grid(row=row, column=col, sticky="w", padx=5, pady=2) def save_settings(self): """Сохраняет все настройки""" try: self.settings.width = self.width_var.get() self.settings.height = self.height_var.get() self.settings.mode = self.mode_var.get() self.settings.terrain_roughness = self.terrain_rough_var.get() self.settings.continent_size = self.continent_size_var.get() self.settings.continent_roughness = self.continent_rough_var.get() self.settings.islands_density = self.islands_dens_var.get() self.settings.islands_roughness = self.islands_rough_var.get() self.settings.island_size = self.island_size_var.get() self.status_var.set("Настройки сохранены") except Exception as e: self.status_var.set(f"Ошибка сохранения: {str(e)}") def generate_land(self): """Генерация карты земли/воды""" try: self.land_generator = WorldGenerator( width=self.settings.width, height=self.settings.height, seed=self.settings.get_valid_seed() ) if self.settings.mode == 'land_only': self.height_map = self.land_generator.generate_land_only() elif self.settings.mode == 'continent': self.height_map = self.land_generator.generate_continent( size=self.settings.continent_size, roughness=self.settings.continent_roughness ) else: self.height_map = self.land_generator.generate_islands( density=self.settings.islands_density, island_size=self.settings.island_size, roughness=self.settings.islands_roughness ) self.biome_map = None self.styled_map_path = None self.status_var.set("Карта земли сгенерирована") except Exception as e: self.status_var.set(f"Ошибка генерации земли: {str(e)}") def generate_height(self): """Генерация карты высот""" if not hasattr(self, 'height_map') or self.height_map is None: self.status_var.set("Ошибка: сначала сгенерируйте карту земли") return try: self.height_generator = HeightmapGenerator( width=self.settings.width, height=self.settings.height, seed=self.settings.get_valid_seed() ) self.height_map = self.height_generator.generate_heightmap( self.height_map, roughness=self.settings.terrain_roughness ) self.biome_map = None self.styled_map_path = None self.status_var.set("Карта высот сгенерирована") except Exception as e: self.status_var.set(f"Ошибка генерации высот: {str(e)}") def generate_climate(self): if not hasattr(self, 'height_map'): self.status_var.set("Ошибка: сначала сгенерируйте карту высот") return try: enabled_biomes = [b for b, var in self.biome_vars.items() if var.get()] if self.settings.climate_mode == 'realistic': from climate_zones_map import ClimateGenerator self.climate_gen = ClimateGenerator(self.height_map, self.settings) else: from climate_zones_artistic import ArtisticClimateGenerator self.climate_gen = ArtisticClimateGenerator(self.height_map, self.settings) self.biome_map = self.climate_gen.generate_biome_map(['water'] + enabled_biomes) self.status_var.set(f"Карта климата ({self.settings.climate_mode}) сгенерирована") except Exception as e: self.status_var.set(f"Ошибка генерации климата: {str(e)}") def show_map(self): """Отображение карты во встроенном окне""" if not hasattr(self, 'biome_map') and not hasattr(self, 'height_map'): self.status_var.set("Нет данных для отображения") return if self.map_window: self.map_window.destroy() self.map_window = tk.Toplevel(self.root) self.map_window.title("Просмотр карты") fig = plt.Figure(figsize=(8, 6)) ax = fig.add_subplot(111) if hasattr(self, 'biome_map') and self.biome_map is not None: ax.imshow(self.biome_map) ax.set_title("Карта биомов") else: ax.imshow(self.height_map, cmap='terrain') ax.set_title("Карта высот") ax.axis('off') self.canvas = FigureCanvasTkAgg(fig, master=self.map_window) self.canvas.draw() self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) # Кнопка закрытия ttk.Button(self.map_window, text="Закрыть", command=self.map_window.destroy).pack(pady=5) if __name__ == "__main__": root = tk.Tk() app = WorldGeneratorApp(root) root.mainloop()