using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using PaperRacing.AStar; namespace PaperRacing.WebService { // Классы для запросов и ответов API public class SolveRequest { public int[][]? map { get; set; } public int? maxIterations { get; set; } // Опциональный параметр public int? timeoutSeconds { get; set; } // Опциональный тайм-аут } public class SolveResponse { public bool success { get; set; } public int[][]? solution { get; set; } public string? error { get; set; } public SolveStatistics? statistics { get; set; } } public class SolveStatistics { public int steps { get; set; } public int checkpoints { get; set; } public int iterations { get; set; } public double computeTimeSeconds { get; set; } public int maxSpeed { get; set; } } public class HealthResponse { public string status { get; set; } = "healthy"; public string version { get; set; } = "1.0.0"; public DateTime timestamp { get; set; } = DateTime.UtcNow; } // Сервис для решения задачи public class RacingSolverService { public SolveResponse Solve(SolveRequest request, int maxIterations = 5000000) { try { if (request.map == null || request.map.Length == 0) { return new SolveResponse { success = false, error = "Map data is required" }; } var startTime = DateTime.Now; // Загружаем карту из переданного JSON var (width, height, start, checkpoints, obstacles, cellTypes) = ParseMap(request.map); // Создаем трек var track = new RaceTrack(width, height, start, checkpoints, obstacles, cellTypes); // Находим решение var solution = track.FindSolution(); var elapsed = DateTime.Now - startTime; if (solution != null) { // Конвертируем решение в формат JSON var accelerations = ConvertPathToAccelerations(solution); // Статистика int maxSpeed = CalculateMaxSpeed(solution); return new SolveResponse { success = true, solution = accelerations, statistics = new SolveStatistics { steps = accelerations.Length, checkpoints = checkpoints.Count, iterations = 0, // Можно добавить счетчик итераций computeTimeSeconds = elapsed.TotalSeconds, maxSpeed = maxSpeed } }; } else { return new SolveResponse { success = false, error = "No solution found within the iteration limit" }; } } catch (Exception ex) { return new SolveResponse { success = false, error = $"Error solving: {ex.Message}" }; } } private (int width, int height, Point start, Dictionary checkpoints, HashSet obstacles, Dictionary cellTypes) ParseMap(int[][] map) { int height = map.Length; int width = map[0].Length; var checkpoints = new Dictionary(); var obstacles = new HashSet(); var cellTypes = new Dictionary(); Point? start = null; int checkpointId = 1; // Проходим по карте (JSON карта идет сверху вниз, поэтому инвертируем Y) for (int jsonY = 0; jsonY < height; jsonY++) { for (int x = 0; x < width; x++) { int cellType = map[jsonY][x]; // Инвертируем Y координату для правильного отображения int y = height - 1 - jsonY; var point = new Point(x, y); // Сохраняем тип клетки cellTypes[point] = cellType; switch (cellType) { case 0: // Дорога if (start == null) start = point; break; case 1: // Камень (препятствие) obstacles.Add(point); break; case 2: // Снег break; case 3: // Лёд break; case 4: // Чекпоинт checkpoints[checkpointId++] = point; break; case 5: // Старт (приоритетнее чем тип 0) start = point; break; } } } if (start == null) throw new Exception("Start position not found (cell type 0 or 5)"); return (width, height, start, checkpoints, obstacles, cellTypes); } private int[][] ConvertPathToAccelerations(List path) { var accelerations = new List(); Point prevVelocity = new Point(0, 0); for (int i = 0; i < path.Count; i++) { Point velocity = i > 0 ? path[i] - path[i - 1] : new Point(0, 0); Point acceleration = velocity - prevVelocity; accelerations.Add(new int[] { acceleration.X, -acceleration.Y }); prevVelocity = velocity; } return accelerations.ToArray(); } private int CalculateMaxSpeed(List solution) { int maxSpeed = 0; for (int i = 1; i < solution.Count; i++) { var velocity = solution[i] - solution[i - 1]; int speed = Math.Abs(velocity.X) + Math.Abs(velocity.Y); maxSpeed = Math.Max(maxSpeed, speed); } return maxSpeed; } } class Program { static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Добавляем сервисы builder.Services.AddSingleton(); builder.Services.AddCors(options => { options.AddPolicy("AllowAll", builder => { builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }); }); // Настраиваем JSON опции builder.Services.ConfigureHttpJsonOptions(options => { options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; options.SerializerOptions.WriteIndented = true; }); var app = builder.Build(); // Middleware app.UseCors("AllowAll"); // Health check endpoint app.MapGet("/health", () => new HealthResponse()); // Информация об API app.MapGet("/", () => new { service = "Paper Racing A* Solver", version = "1.0.0", endpoints = new { health = "GET /health", solve = "POST /solve", info = "GET /" }, documentation = "POST /solve with JSON body containing 'map' field (2D array of integers)" }); // Основной endpoint для решения задачи app.MapPost("/solve", (SolveRequest request, RacingSolverService solver) => { Console.WriteLine($"\n[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Received solve request"); if (request.map == null || request.map.Length == 0) { Console.WriteLine(" ❌ Invalid request: map is empty"); return Results.BadRequest(new SolveResponse { success = false, error = "Map data is required" }); } Console.WriteLine($" Map size: {request.map[0].Length}x{request.map.Length}"); var response = solver.Solve(request, request.maxIterations ?? 5000000); if (response.success) { Console.WriteLine($" ✓ Solution found: {response.statistics?.steps} steps in {response.statistics?.computeTimeSeconds:F2}s"); } else { Console.WriteLine($" ❌ Solution not found: {response.error}"); } return response.success ? Results.Ok(response) : Results.Ok(response); }); // Запускаем сервер var port = Environment.GetEnvironmentVariable("PORT") ?? "5000"; Console.WriteLine("╔════════════════════════════════════════════════════════════╗"); Console.WriteLine("║ Paper Racing A* Solver - Web Service ║"); Console.WriteLine("╚════════════════════════════════════════════════════════════╝"); Console.WriteLine($"\n🚀 Server starting on http://localhost:{port}"); Console.WriteLine($"\nEndpoints:"); Console.WriteLine($" GET http://localhost:{port}/ - API info"); Console.WriteLine($" GET http://localhost:{port}/health - Health check"); Console.WriteLine($" POST http://localhost:{port}/solve - Solve racing map"); Console.WriteLine($"\nPress Ctrl+C to stop\n"); app.Run($"http://0.0.0.0:{port}"); } } }