init
This commit is contained in:
649
ProgramAStar.cs
Normal file
649
ProgramAStar.cs
Normal file
@@ -0,0 +1,649 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace PaperRacing.AStar
|
||||
{
|
||||
// Представляет точку на поле
|
||||
public record Point(int X, int Y)
|
||||
{
|
||||
public static Point operator +(Point a, Point b) => new(a.X + b.X, a.Y + b.Y);
|
||||
public static Point operator -(Point a, Point b) => new(a.X - b.X, a.Y - b.Y);
|
||||
|
||||
public double DistanceTo(Point other)
|
||||
{
|
||||
return Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2));
|
||||
}
|
||||
|
||||
public int ManhattanDistance(Point other)
|
||||
{
|
||||
return Math.Abs(X - other.X) + Math.Abs(Y - other.Y);
|
||||
}
|
||||
}
|
||||
|
||||
// Состояние игры с поддержкой A*
|
||||
public class GameState
|
||||
{
|
||||
public Point Position { get; init; }
|
||||
public Point Velocity { get; init; }
|
||||
public HashSet<int> VisitedCheckpoints { get; init; }
|
||||
public List<Point> Path { get; init; }
|
||||
public int GCost { get; init; } // Фактическая стоимость (количество шагов)
|
||||
public double HCost { get; set; } // Эвристическая стоимость
|
||||
public double FCost => GCost + HCost; // Полная стоимость
|
||||
|
||||
public GameState(Point position, Point velocity, HashSet<int> visitedCheckpoints, List<Point> path, int gCost)
|
||||
{
|
||||
Position = position;
|
||||
Velocity = velocity;
|
||||
VisitedCheckpoints = visitedCheckpoints;
|
||||
Path = path;
|
||||
GCost = gCost;
|
||||
}
|
||||
|
||||
public string GetKey()
|
||||
{
|
||||
var checkpointsMask = string.Join(",", VisitedCheckpoints.OrderBy(x => x));
|
||||
return $"{Position.X},{Position.Y}|{Velocity.X},{Velocity.Y}|{checkpointsMask}";
|
||||
}
|
||||
}
|
||||
|
||||
// Компаратор для очереди с приоритетом
|
||||
public class GameStateComparer : IComparer<GameState>
|
||||
{
|
||||
public int Compare(GameState? x, GameState? y)
|
||||
{
|
||||
if (x == null || y == null) return 0;
|
||||
int fCostCompare = x.FCost.CompareTo(y.FCost);
|
||||
if (fCostCompare != 0) return fCostCompare;
|
||||
return y.GCost.CompareTo(x.GCost); // При равных FCost предпочитаем больший GCost
|
||||
}
|
||||
}
|
||||
|
||||
// Игровое поле с алгоритмом A*
|
||||
public class RaceTrack
|
||||
{
|
||||
private readonly int _width;
|
||||
private readonly int _height;
|
||||
private readonly HashSet<Point> _obstacles;
|
||||
private readonly Dictionary<int, Point> _checkpoints;
|
||||
private readonly Point _start;
|
||||
private readonly Dictionary<Point, int> _cellTypes; // Тип клетки для каждой точки
|
||||
|
||||
public RaceTrack(int width, int height, Point start, Dictionary<int, Point> checkpoints, HashSet<Point> obstacles, Dictionary<Point, int> cellTypes)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
_start = start;
|
||||
_checkpoints = checkpoints;
|
||||
_obstacles = obstacles;
|
||||
_cellTypes = cellTypes;
|
||||
}
|
||||
|
||||
private bool IsInBounds(Point p) => p.X >= 0 && p.X < _width && p.Y >= 0 && p.Y < _height;
|
||||
|
||||
// Получить диапазон допустимых ускорений в зависимости от типа клетки
|
||||
private (int minAccel, int maxAccel) GetAccelerationRange(Point position)
|
||||
{
|
||||
if (_cellTypes.TryGetValue(position, out int cellType))
|
||||
{
|
||||
return cellType switch
|
||||
{
|
||||
2 => (-1, 1), // Снег: ускорение от -1 до +1
|
||||
3 => (0, 0), // Лёд: ускорение нельзя менять
|
||||
_ => (-2, 2) // Обычная дорога, чекпоинт, старт: ускорение от -2 до +2
|
||||
};
|
||||
}
|
||||
return (-2, 2); // По умолчанию как обычная дорога
|
||||
}
|
||||
|
||||
// Эвристическая функция: оценка оставшегося расстояния
|
||||
private double CalculateHeuristic(Point position, Point velocity, HashSet<int> visitedCheckpoints)
|
||||
{
|
||||
// Находим непосещенные чекпоинты
|
||||
var unvisited = _checkpoints.Where(kv => !visitedCheckpoints.Contains(kv.Key)).ToList();
|
||||
|
||||
if (unvisited.Count == 0)
|
||||
return 0;
|
||||
|
||||
// Простая эвристика: расстояние до ближайшего + сумма расстояний между оставшимися
|
||||
if (unvisited.Count == 1)
|
||||
{
|
||||
// Console.WriteLine($"Last checkpoint!");
|
||||
double distToCheckpoint = position.DistanceTo(unvisited[0].Value);
|
||||
double currentSpeed = Math.Sqrt(velocity.X * velocity.X + velocity.Y * velocity.Y);
|
||||
double maxAcceleration = Math.Sqrt(2); // Максимальное ускорение по диагонали
|
||||
|
||||
// Оценка: сколько шагов нужно для достижения с учетом текущей скорости
|
||||
return EstimateStepsToReach(distToCheckpoint, currentSpeed, maxAcceleration, true);
|
||||
}
|
||||
|
||||
// Для нескольких чекпоинтов используем жадную эвристику TSP
|
||||
double totalCost = 0;
|
||||
var current = position;
|
||||
var remaining = new List<Point>(unvisited.Select(kv => kv.Value));
|
||||
double speed = Math.Sqrt(velocity.X * velocity.X + velocity.Y * velocity.Y);
|
||||
|
||||
while (remaining.Count > 0)
|
||||
{
|
||||
// Находим ближайший непосещенный чекпоинт
|
||||
var nearest = remaining.OrderBy(p => current.DistanceTo(p)).First();
|
||||
double dist = current.DistanceTo(nearest);
|
||||
totalCost += EstimateStepsToReach(dist, speed, Math.Sqrt(2));
|
||||
|
||||
current = nearest;
|
||||
remaining.Remove(nearest);
|
||||
speed = 4.0; // Предполагаем более высокую среднюю скорость для следующих сегментов
|
||||
}
|
||||
|
||||
// Агрессивная эвристика для 40 чекпоинтов - множитель 0.7
|
||||
return totalCost;
|
||||
}
|
||||
|
||||
// Оценка количества шагов для достижения расстояния
|
||||
private double EstimateStepsToReach(double distance, double currentSpeed, double maxAcceleration, bool isLastCheckpoint = false)
|
||||
{
|
||||
if (distance <= 0)
|
||||
return 0;
|
||||
|
||||
// Упрощенная физическая модель: можно ускоряться на maxAcceleration каждый шаг
|
||||
// v = v0 + a*t, s = v0*t + 0.5*a*t^2
|
||||
// Решаем квадратное уравнение: 0.5*a*t^2 + v0*t - s = 0
|
||||
|
||||
double a = maxAcceleration;
|
||||
double v0 = currentSpeed;
|
||||
double s = distance;
|
||||
|
||||
// t = (-v0 + sqrt(v0^2 + 2*a*s)) / a
|
||||
double discriminant = v0 * v0 + 2 * a * s;
|
||||
if (discriminant < 0)
|
||||
return distance / (v0 + 0.1); // Fallback
|
||||
|
||||
double steps = (-v0 + Math.Sqrt(discriminant)) / a;
|
||||
|
||||
if (isLastCheckpoint)
|
||||
{
|
||||
return steps;
|
||||
}
|
||||
|
||||
// Учитываем, что нужно еще замедлиться
|
||||
double brakingSteps = currentSpeed > 0 ? currentSpeed / maxAcceleration : 0;
|
||||
|
||||
return Math.Max(1, steps + brakingSteps * 0.5);
|
||||
}
|
||||
|
||||
// A* алгоритм
|
||||
public List<Point>? FindSolution()
|
||||
{
|
||||
var openSet = new SortedSet<GameState>(new GameStateComparer());
|
||||
var openSetLookup = new Dictionary<string, GameState>();
|
||||
var closedSet = new HashSet<string>();
|
||||
|
||||
var initialState = new GameState(_start, new Point(0, 0), new HashSet<int>(), new List<Point> { _start }, 0);
|
||||
initialState.HCost = CalculateHeuristic(_start, new Point(0, 0), new HashSet<int>());
|
||||
|
||||
openSet.Add(initialState);
|
||||
openSetLookup[initialState.GetKey()] = initialState;
|
||||
|
||||
int iterations = 0;
|
||||
const int maxIterations = 5000000; // Увеличено для 40 чекпоинтов
|
||||
int maxOpenSetSize = 0;
|
||||
|
||||
Console.WriteLine($"Начальная эвристика: {initialState.HCost:F2}");
|
||||
|
||||
while (openSet.Count > 0 && iterations < maxIterations)
|
||||
{
|
||||
iterations++;
|
||||
maxOpenSetSize = Math.Max(maxOpenSetSize, openSet.Count);
|
||||
|
||||
if (iterations % 10000 == 0)
|
||||
{
|
||||
var current = openSet.Min!;
|
||||
Console.WriteLine($"Итерация {iterations}: OpenSet={openSet.Count}, FCost={current.FCost:F2}, GCost={current.GCost}, Посещено={current.VisitedCheckpoints.Count}/{_checkpoints.Count}");
|
||||
}
|
||||
|
||||
var currentState = openSet.Min!;
|
||||
openSet.Remove(currentState);
|
||||
openSetLookup.Remove(currentState.GetKey());
|
||||
|
||||
// Проверяем, собрали ли все чекпоинты
|
||||
if (currentState.VisitedCheckpoints.Count == _checkpoints.Count)
|
||||
{
|
||||
Console.WriteLine($"\n=== Решение найдено ===");
|
||||
Console.WriteLine($"Итераций: {iterations}");
|
||||
Console.WriteLine($"Максимальный размер открытого множества: {maxOpenSetSize}");
|
||||
Console.WriteLine($"Количество ходов: {currentState.GCost}");
|
||||
Console.WriteLine($"Финальная стоимость: {currentState.FCost:F2}");
|
||||
return currentState.Path;
|
||||
}
|
||||
|
||||
closedSet.Add(currentState.GetKey());
|
||||
|
||||
// Генерируем все возможные ускорения в зависимости от типа клетки
|
||||
var (minAccel, maxAccel) = GetAccelerationRange(currentState.Position);
|
||||
|
||||
for (int dx = minAccel; dx <= maxAccel; dx++)
|
||||
{
|
||||
for (int dy = minAccel; dy <= maxAccel; dy++)
|
||||
{
|
||||
var acceleration = new Point(dx, dy);
|
||||
var newVelocity = currentState.Velocity + acceleration;
|
||||
var newPosition = currentState.Position + newVelocity;
|
||||
|
||||
if (!IsInBounds(newPosition))
|
||||
continue;
|
||||
|
||||
// Можно проезжать через препятствия, но нельзя на них останавливаться
|
||||
if (_obstacles.Contains(newPosition))
|
||||
continue;
|
||||
|
||||
// Проверяем чекпоинты
|
||||
var newCheckpoints = new HashSet<int>(currentState.VisitedCheckpoints);
|
||||
foreach (var (id, checkpoint) in _checkpoints)
|
||||
{
|
||||
if (!newCheckpoints.Contains(id) && newPosition.Equals(checkpoint))
|
||||
{
|
||||
newCheckpoints.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
var newPath = new List<Point>(currentState.Path) { newPosition };
|
||||
var newState = new GameState(newPosition, newVelocity, newCheckpoints, newPath, currentState.GCost + 1);
|
||||
newState.HCost = CalculateHeuristic(newPosition, newVelocity, newCheckpoints);
|
||||
|
||||
var key = newState.GetKey();
|
||||
|
||||
if (closedSet.Contains(key))
|
||||
continue;
|
||||
|
||||
// Проверяем, есть ли уже такое состояние в открытом множестве
|
||||
if (openSetLookup.TryGetValue(key, out var existingState))
|
||||
{
|
||||
// Если новый путь лучше, обновляем
|
||||
if (newState.GCost < existingState.GCost)
|
||||
{
|
||||
openSet.Remove(existingState);
|
||||
openSet.Add(newState);
|
||||
openSetLookup[key] = newState;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
openSet.Add(newState);
|
||||
openSetLookup[key] = newState;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"\nРешение не найдено после {iterations} итераций");
|
||||
Console.WriteLine($"Максимальный размер открытого множества: {maxOpenSetSize}");
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Visualize(List<Point>? path = null)
|
||||
{
|
||||
var pathSet = path != null ? new HashSet<Point>(path) : new HashSet<Point>();
|
||||
|
||||
for (int y = _height - 1; y >= 0; y--)
|
||||
{
|
||||
Console.Write($"{y:00}|");
|
||||
for (int x = 0; x < _width; x++)
|
||||
{
|
||||
var point = new Point(x, y);
|
||||
|
||||
if (point.Equals(_start))
|
||||
Console.Write("S ");
|
||||
else if (_checkpoints.Values.Contains(point))
|
||||
{
|
||||
int checkpointId = _checkpoints.First(kv => kv.Value.Equals(point)).Key;
|
||||
// Для чисел > 9 показываем символ
|
||||
if (checkpointId < 10)
|
||||
Console.Write($"{checkpointId} ");
|
||||
else
|
||||
Console.Write("● "); // Точка для всех чекпоинтов >= 10
|
||||
}
|
||||
else if (_obstacles.Contains(point))
|
||||
Console.Write("# ");
|
||||
else if (pathSet.Contains(point))
|
||||
Console.Write(". ");
|
||||
else if (_cellTypes.TryGetValue(point, out int cellType))
|
||||
{
|
||||
// Показываем тип поверхности
|
||||
switch (cellType)
|
||||
{
|
||||
case 2: // Снег
|
||||
Console.Write("~ ");
|
||||
break;
|
||||
case 3: // Лёд
|
||||
Console.Write("= ");
|
||||
break;
|
||||
default:
|
||||
Console.Write(" ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
Console.Write(" ");
|
||||
}
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
// Ось X
|
||||
Console.Write(" ");
|
||||
for (int x = 0; x < _width; x++)
|
||||
{
|
||||
if (x % 5 == 0)
|
||||
Console.Write($"{x / 10}");
|
||||
else
|
||||
Console.Write(" ");
|
||||
}
|
||||
Console.WriteLine();
|
||||
Console.Write(" ");
|
||||
for (int x = 0; x < _width; x++)
|
||||
{
|
||||
Console.Write($"{x % 10}");
|
||||
}
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
public void ShowPath(List<Point> path)
|
||||
{
|
||||
Console.WriteLine("\nДетальный путь решения:");
|
||||
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;
|
||||
|
||||
string checkpoint = "";
|
||||
foreach (var (id, pos) in _checkpoints)
|
||||
{
|
||||
if (pos.Equals(path[i]))
|
||||
{
|
||||
checkpoint = $" ✓ ЧЕКПОИНТ #{id}";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"Шаг {i,3}: Поз=({path[i].X,2},{path[i].Y,2}) Скор=({velocity.X,2},{velocity.Y,2}) Ускор=({acceleration.X,2},{acceleration.Y,2}){checkpoint}");
|
||||
prevVelocity = velocity;
|
||||
}
|
||||
}
|
||||
|
||||
public void ExportSolutionToJson(List<Point> path, string filePath)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
var solution = new { solution = accelerations };
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
string jsonContent = JsonSerializer.Serialize(solution, options);
|
||||
File.WriteAllText(filePath, jsonContent);
|
||||
|
||||
Console.WriteLine($"\n✓ Решение экспортировано в файл: {filePath}");
|
||||
Console.WriteLine($" Количество шагов: {accelerations.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
// Классы для десериализации JSON
|
||||
public class MapData
|
||||
{
|
||||
public int[][]? map { get; set; }
|
||||
}
|
||||
|
||||
// Загрузчик карт из JSON
|
||||
public class MapLoader
|
||||
{
|
||||
public static (int width, int height, Point start, Dictionary<int, Point> checkpoints, HashSet<Point> obstacles, Dictionary<Point, int> cellTypes)
|
||||
LoadFromJson(string filePath)
|
||||
{
|
||||
string jsonContent = File.ReadAllText(filePath);
|
||||
var mapData = JsonSerializer.Deserialize<MapData>(jsonContent);
|
||||
|
||||
if (mapData?.map == null)
|
||||
throw new Exception("Не удалось загрузить карту из файла");
|
||||
|
||||
int height = mapData.map.Length;
|
||||
int width = mapData.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;
|
||||
int snowCount = 0;
|
||||
int iceCount = 0;
|
||||
|
||||
// Проходим по карте (JSON карта идет сверху вниз, поэтому инвертируем Y)
|
||||
for (int jsonY = 0; jsonY < height; jsonY++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
int cellType = mapData.map[jsonY][x];
|
||||
// Инвертируем Y координату для правильного отображения
|
||||
int y = height - 1 - jsonY;
|
||||
var point = new Point(x, y);
|
||||
|
||||
// Сохраняем тип клетки
|
||||
cellTypes[point] = cellType;
|
||||
|
||||
switch (cellType)
|
||||
{
|
||||
case 0: // Дорога
|
||||
// Первая дорога становится стартом, если старт еще не задан и нет явного старта (тип 5)
|
||||
if (start == null)
|
||||
start = point;
|
||||
break;
|
||||
case 1: // Камень (препятствие)
|
||||
obstacles.Add(point);
|
||||
break;
|
||||
case 2: // Снег
|
||||
snowCount++;
|
||||
break;
|
||||
case 3: // Лёд
|
||||
iceCount++;
|
||||
break;
|
||||
case 4: // Чекпоинт
|
||||
checkpoints[checkpointId++] = point;
|
||||
break;
|
||||
case 5: // Старт (приоритетнее чем тип 0)
|
||||
start = point;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (start == null)
|
||||
throw new Exception("Не найдена стартовая позиция (ячейка типа 0 или 5)");
|
||||
|
||||
Console.WriteLine($"Загружена карта: {width}x{height}");
|
||||
Console.WriteLine($"Старт: ({start.X}, {start.Y})");
|
||||
Console.WriteLine($"Чекпоинтов: {checkpoints.Count}");
|
||||
Console.WriteLine($"Препятствий: {obstacles.Count}");
|
||||
Console.WriteLine($"Снег: {snowCount} клеток");
|
||||
Console.WriteLine($"Лёд: {iceCount} клеток");
|
||||
|
||||
return (width, height, start, checkpoints, obstacles, cellTypes);
|
||||
}
|
||||
}
|
||||
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("╔════════════════════════════════════════╗");
|
||||
Console.WriteLine("║ Гонки на бумаге - Алгоритм A* ║");
|
||||
Console.WriteLine("╚════════════════════════════════════════╝\n");
|
||||
|
||||
// Обработка аргументов командной строки
|
||||
string? mapFilePath = null;
|
||||
string? outputFilePath = null;
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (args[i] == "--output" || args[i] == "-o")
|
||||
{
|
||||
if (i + 1 < args.Length)
|
||||
{
|
||||
outputFilePath = args[i + 1];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
else if (mapFilePath == null && File.Exists(args[i]))
|
||||
{
|
||||
mapFilePath = args[i];
|
||||
}
|
||||
}
|
||||
|
||||
int width, height;
|
||||
Point start;
|
||||
Dictionary<int, Point> checkpoints;
|
||||
HashSet<Point> obstacles;
|
||||
Dictionary<Point, int> cellTypes;
|
||||
|
||||
// Проверяем, передан ли путь к файлу карты
|
||||
if (mapFilePath != null)
|
||||
{
|
||||
Console.WriteLine($"Загрузка карты из файла: {mapFilePath}\n");
|
||||
try
|
||||
{
|
||||
(width, height, start, checkpoints, obstacles, cellTypes) = MapLoader.LoadFromJson(mapFilePath);
|
||||
Console.WriteLine();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Ошибка загрузки карты: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Используем встроенную карту по умолчанию
|
||||
if (args.Length > 0 && !args.Any(a => a == "--output" || a == "-o"))
|
||||
{
|
||||
Console.WriteLine($"⚠️ Файл карты не найден. Используется встроенная карта.\n");
|
||||
}
|
||||
else if (args.Length == 0)
|
||||
{
|
||||
Console.WriteLine("Используется встроенная карта.\n");
|
||||
Console.WriteLine("Использование:");
|
||||
Console.WriteLine(" racing-astar <map-file.json> [--output|-o <output-file.json>]\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Используется встроенная карта.\n");
|
||||
}
|
||||
|
||||
// Создаем поле 210x50 (длинная прямая для 40 чекпоинтов)
|
||||
width = 210;
|
||||
height = 50;
|
||||
|
||||
// Стартовая позиция
|
||||
start = new Point(2, 2);
|
||||
|
||||
// Чекпоинты - 40 штук вдоль ОДНОЙ линии (максимально упрощаем)
|
||||
checkpoints = new Dictionary<int, Point>();
|
||||
for (int i = 1; i <= 40; i++)
|
||||
{
|
||||
// Все чекпоинты вдоль одной линии на расстоянии 5 клеток друг от друга
|
||||
checkpoints[i] = new Point(5 + (i - 1) * 5, 40);
|
||||
}
|
||||
|
||||
// Препятствия - НЕТ! (для 40 чекпоинтов убираем препятствия для упрощения)
|
||||
obstacles = new HashSet<Point>();
|
||||
|
||||
// Типы клеток - вся карта обычная дорога (0)
|
||||
cellTypes = new Dictionary<Point, int>();
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
cellTypes[new Point(x, y)] = 0; // Обычная дорога
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Создаем трек
|
||||
var track = new RaceTrack(width, height, start, checkpoints, obstacles, cellTypes);
|
||||
|
||||
Console.WriteLine("Начальное поле:");
|
||||
Console.WriteLine($"S - старт, 1-{checkpoints.Count} - чекпоинты ({checkpoints.Count} шт.)");
|
||||
Console.WriteLine("# - препятствия (можно проезжать, нельзя останавливаться)");
|
||||
Console.WriteLine("~ - снег (ускорение ±1), = - лёд (ускорение нельзя менять)\n");
|
||||
track.Visualize();
|
||||
|
||||
Console.WriteLine("\n" + new string('═', 50));
|
||||
Console.WriteLine("Запуск алгоритма A* с эвристикой...");
|
||||
Console.WriteLine(new string('═', 50) + "\n");
|
||||
|
||||
var startTime = DateTime.Now;
|
||||
var solution = track.FindSolution();
|
||||
var elapsed = DateTime.Now - startTime;
|
||||
|
||||
if (solution != null)
|
||||
{
|
||||
Console.WriteLine($"\nВремя работы: {elapsed.TotalSeconds:F2} сек");
|
||||
Console.WriteLine("\n" + new string('═', 50));
|
||||
Console.WriteLine("ВИЗУАЛИЗАЦИЯ РЕШЕНИЯ:");
|
||||
Console.WriteLine(new string('═', 50) + "\n");
|
||||
track.Visualize(solution);
|
||||
track.ShowPath(solution);
|
||||
|
||||
// Статистика
|
||||
Console.WriteLine("\n" + new string('═', 50));
|
||||
Console.WriteLine("СТАТИСТИКА:");
|
||||
Console.WriteLine(new string('═', 50));
|
||||
Console.WriteLine($"Всего ходов: {solution.Count - 1}");
|
||||
Console.WriteLine($"Чекпоинтов собрано: {checkpoints.Count}");
|
||||
|
||||
// Расчет максимальной скорости
|
||||
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);
|
||||
}
|
||||
Console.WriteLine($"Максимальная скорость: {maxSpeed}");
|
||||
|
||||
// Экспорт решения в JSON, если указан файл выгрузки
|
||||
if (outputFilePath != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
track.ExportSolutionToJson(solution, outputFilePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"\n❌ Ошибка экспорта решения: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"\nВремя работы: {elapsed.TotalSeconds:F2} сек");
|
||||
Console.WriteLine("\n❌ Решение не найдено!");
|
||||
Console.WriteLine("Попробуйте упростить задачу или увеличить maxIterations");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user