Files
paper-racing-gpi/ProgramWebService.cs
2025-10-20 23:07:10 +05:00

293 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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}");
}
}
}