| """
|
| Сервис генерации судоку с использованием нейросети
|
| """
|
| import numpy as np
|
| import random
|
| from typing import Tuple, Dict, Optional
|
| import logging
|
|
|
| from app.models.neural_solver import SudokuNeuralSolver
|
| from app.models.validator import SudokuValidator
|
| from app.core.config import settings
|
|
|
| logger = logging.getLogger(__name__)
|
|
|
| class SudokuGeneratorService:
|
| """Сервис для генерации судоку с использованием нейросети"""
|
|
|
| def __init__(self):
|
| """Инициализация сервиса"""
|
| logger.info("Initializing Sudoku Generator Service with Neural Network...")
|
| self.solver = SudokuNeuralSolver()
|
| self.validator = SudokuValidator()
|
| self.empty_cells = {
|
| "easy": settings.EMPTY_CELLS_EASY,
|
| "medium": settings.EMPTY_CELLS_MEDIUM,
|
| "hard": settings.EMPTY_CELLS_HARD
|
| }
|
| logger.info(f"✅ Generator service ready with {settings.MODEL_NAME}")
|
|
|
| def generate_puzzle(
|
| self,
|
| difficulty: str = "medium"
|
| ) -> Tuple[np.ndarray, np.ndarray, Dict]:
|
| """
|
| Генерация судоку с использованием нейросети
|
| """
|
| try:
|
|
|
| logger.info("Generating solution using neural network...")
|
| solution = self._generate_solution_neural()
|
|
|
|
|
| empty_count = self.empty_cells[difficulty]
|
| logger.info(f"Creating puzzle with {empty_count} empty cells")
|
| puzzle = self._create_puzzle_from_solution(solution, empty_count)
|
|
|
|
|
| neural_solution, confidence = self.solver.solve_with_confidence(puzzle)
|
|
|
|
|
| max_attempts = 3
|
| attempts = 0
|
|
|
| while confidence < 0.9 and attempts < max_attempts:
|
| logger.info(f"Regenerating puzzle (attempt {attempts + 1}), confidence: {confidence:.2f}")
|
| puzzle = self._create_puzzle_from_solution(solution, empty_count)
|
| neural_solution, confidence = self.solver.solve_with_confidence(puzzle)
|
| attempts += 1
|
|
|
|
|
| metadata = {
|
| "model": settings.MODEL_NAME,
|
| "difficulty": difficulty,
|
| "confidence": float(confidence),
|
| "empty_cells": empty_count,
|
| "attempts": attempts + 1,
|
| "neural_network": "SudokuNet"
|
| }
|
|
|
| logger.info(f"✅ Generated {difficulty} puzzle with confidence {confidence:.2f}")
|
|
|
| return puzzle, solution, metadata
|
|
|
| except Exception as e:
|
| logger.error(f"Error generating puzzle: {e}")
|
| return self._get_fallback_puzzle(difficulty)
|
|
|
| def _generate_solution_neural(self) -> np.ndarray:
|
| """
|
| Генерирует решение через нейросеть
|
| """
|
|
|
| grid = np.zeros((9, 9), dtype=int)
|
|
|
|
|
| for _ in range(10):
|
| row, col = random.randint(0, 8), random.randint(0, 8)
|
| num = random.randint(1, 9)
|
| if self.validator._is_safe(grid, row, col, num):
|
| grid[row, col] = num
|
|
|
|
|
| try:
|
| solution = self.solver.solve(grid)
|
| logger.info("Neural network generated solution successfully")
|
| return solution
|
| except Exception as e:
|
| logger.warning(f"Neural solution failed, using backtracking: {e}")
|
| return self._generate_solution_backtracking()
|
|
|
| def _generate_solution_backtracking(self) -> np.ndarray:
|
| """Запасной метод генерации через backtracking"""
|
| grid = np.zeros((9, 9), dtype=int)
|
|
|
|
|
| for i in range(0, 9, 3):
|
| self._fill_box(grid, i, i)
|
|
|
|
|
| self._solve_grid(grid)
|
| return grid
|
|
|
| def _fill_box(self, grid: np.ndarray, row: int, col: int):
|
| """Заполняет блок 3x3 случайными числами"""
|
| nums = list(range(1, 10))
|
| random.shuffle(nums)
|
|
|
| for i in range(3):
|
| for j in range(3):
|
| grid[row + i, col + j] = nums[i * 3 + j]
|
|
|
| def _solve_grid(self, grid: np.ndarray) -> bool:
|
| """Решает судоку через backtracking"""
|
| empty = self.validator._find_empty(grid)
|
| if not empty:
|
| return True
|
|
|
| row, col = empty
|
| nums = list(range(1, 10))
|
| random.shuffle(nums)
|
|
|
| for num in nums:
|
| if self.validator._is_safe(grid, row, col, num):
|
| grid[row, col] = num
|
| if self._solve_grid(grid):
|
| return True
|
| grid[row, col] = 0
|
|
|
| return False
|
|
|
| def _create_puzzle_from_solution(
|
| self,
|
| solution: np.ndarray,
|
| empty_count: int
|
| ) -> np.ndarray:
|
| """Создает пазл из решения"""
|
| puzzle = solution.copy()
|
| positions = list(range(81))
|
| random.shuffle(positions)
|
|
|
| for idx in positions[:empty_count]:
|
| row, col = idx // 9, idx % 9
|
| puzzle[row, col] = 0
|
|
|
| return puzzle
|
|
|
| def generate_adaptive_puzzle(
|
| self,
|
| user_id: int,
|
| difficulty: str = "medium"
|
| ) -> Tuple[np.ndarray, np.ndarray, Dict]:
|
| """
|
| Генерация с учетом уровня игрока (адаптивная)
|
| """
|
|
|
| puzzle, solution, metadata = self.generate_puzzle(difficulty)
|
|
|
|
|
| empty_cells = np.sum(puzzle == 0)
|
|
|
|
|
|
|
|
|
| metadata["player_id"] = user_id
|
| metadata["adaptive"] = True
|
| metadata["empty_cells_actual"] = int(empty_cells)
|
|
|
| return puzzle, solution, metadata
|
|
|
| def _get_fallback_puzzle(self, difficulty: str) -> Tuple[np.ndarray, np.ndarray, Dict]:
|
| """Возвращает тестовое судоку в случае ошибки"""
|
| logger.warning("Using fallback puzzle due to error")
|
|
|
| puzzle = [
|
| [5,3,0,0,7,0,0,0,0],
|
| [6,0,0,1,9,5,0,0,0],
|
| [0,9,8,0,0,0,0,6,0],
|
| [8,0,0,0,6,0,0,0,3],
|
| [4,0,0,8,0,3,0,0,1],
|
| [7,0,0,0,2,0,0,0,6],
|
| [0,6,0,0,0,0,2,8,0],
|
| [0,0,0,4,1,9,0,0,5],
|
| [0,0,0,0,8,0,0,7,9]
|
| ]
|
|
|
| solution = [
|
| [5,3,4,6,7,8,9,1,2],
|
| [6,7,2,1,9,5,3,4,8],
|
| [1,9,8,3,4,2,5,6,7],
|
| [8,5,9,7,6,1,4,2,3],
|
| [4,2,6,8,5,3,7,9,1],
|
| [7,1,3,9,2,4,8,5,6],
|
| [9,6,1,5,3,7,2,8,4],
|
| [2,8,7,4,1,9,6,3,5],
|
| [3,4,5,2,8,6,1,7,9]
|
| ]
|
|
|
| metadata = {
|
| "model": "fallback",
|
| "difficulty": difficulty,
|
| "confidence": 1.0,
|
| "empty_cells": 30,
|
| "error": "Using fallback"
|
| }
|
|
|
| return np.array(puzzle), np.array(solution), metadata |