293 lines
11 KiB
C#
293 lines
11 KiB
C#
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<int, Point> checkpoints, HashSet<Point> obstacles, Dictionary<Point, int> cellTypes)
|
||
ParseMap(int[][] map)
|
||
{
|
||
int height = map.Length;
|
||
int width = map[0].Length;
|
||
|
||
var checkpoints = new Dictionary<int, Point>();
|
||
var obstacles = new HashSet<Point>();
|
||
var cellTypes = new Dictionary<Point, int>();
|
||
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<Point> path)
|
||
{
|
||
var accelerations = new List<int[]>();
|
||
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<Point> 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<RacingSolverService>();
|
||
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}");
|
||
}
|
||
}
|
||
}
|
||
|