using System; using System.Collections.Generic; using System.Linq; namespace PaperRacing { // Представляет точку на поле 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 class GameState { public Point Position { get; init; } public Point Velocity { get; init; } public HashSet VisitedCheckpoints { get; init; } public List Path { get; init; } public GameState(Point position, Point velocity, HashSet visitedCheckpoints, List path) { Position = position; Velocity = velocity; VisitedCheckpoints = visitedCheckpoints; Path = path; } // Уникальный ключ для состояния (без учета пути) public string GetKey() { var checkpointsMask = string.Join(",", VisitedCheckpoints.OrderBy(x => x)); return $"{Position.X},{Position.Y}|{Velocity.X},{Velocity.Y}|{checkpointsMask}"; } } // Игровое поле public class RaceTrack { private readonly int _width; private readonly int _height; private readonly HashSet _obstacles; private readonly Dictionary _checkpoints; private readonly Point _start; public RaceTrack(int width, int height, Point start, Dictionary checkpoints, HashSet obstacles) { _width = width; _height = height; _start = start; _checkpoints = checkpoints; _obstacles = obstacles; } // Проверка, находится ли точка в границах поля private bool IsInBounds(Point p) => p.X >= 0 && p.X < _width && p.Y >= 0 && p.Y < _height; // Проверка пересечения отрезка с препятствиями (алгоритм Брезенхема) private bool IntersectsObstacle(Point from, Point to) { int x0 = from.X, y0 = from.Y; int x1 = to.X, y1 = to.Y; int dx = Math.Abs(x1 - x0); int dy = Math.Abs(y1 - y0); int sx = x0 < x1 ? 1 : -1; int sy = y0 < y1 ? 1 : -1; int err = dx - dy; while (true) { if (_obstacles.Contains(new Point(x0, y0))) return true; if (x0 == x1 && y0 == y1) break; int e2 = 2 * err; if (e2 > -dy) { err -= dy; x0 += sx; } if (e2 < dx) { err += dx; y0 += sy; } } return false; } // Поиск оптимального решения методом BFS public List? FindSolution() { var queue = new Queue(); var visited = new HashSet(); var initialState = new GameState(_start, new Point(0, 0), new HashSet(), new List { _start }); queue.Enqueue(initialState); visited.Add(initialState.GetKey()); int iterations = 0; const int maxIterations = 1000000; while (queue.Count > 0 && iterations < maxIterations) { iterations++; var current = queue.Dequeue(); // Проверяем, собрали ли все чекпоинты if (current.VisitedCheckpoints.Count == _checkpoints.Count) { Console.WriteLine($"Решение найдено за {iterations} итераций"); Console.WriteLine($"Количество ходов: {current.Path.Count - 1}"); return current.Path; } // Генерируем все возможные ускорения (-1, 0, +1 по каждой оси) for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { var acceleration = new Point(dx, dy); var newVelocity = current.Velocity + acceleration; var newPosition = current.Position + newVelocity; // Проверяем границы if (!IsInBounds(newPosition)) continue; // Проверяем препятствия if (IntersectsObstacle(current.Position, newPosition)) continue; // Проверяем чекпоинты var newCheckpoints = new HashSet(current.VisitedCheckpoints); foreach (var (id, checkpoint) in _checkpoints) { if (!newCheckpoints.Contains(id) && newPosition.Equals(checkpoint)) { newCheckpoints.Add(id); } } var newPath = new List(current.Path) { newPosition }; var newState = new GameState(newPosition, newVelocity, newCheckpoints, newPath); var key = newState.GetKey(); if (!visited.Contains(key)) { visited.Add(key); queue.Enqueue(newState); } } } } Console.WriteLine($"Решение не найдено после {iterations} итераций"); return null; } // Визуализация поля public void Visualize(List? path = null) { var pathSet = path != null ? new HashSet(path) : new HashSet(); 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)) Console.Write($"{_checkpoints.First(kv => kv.Value.Equals(point)).Key} "); else if (_obstacles.Contains(point)) Console.Write("# "); else if (pathSet.Contains(point)) Console.Write(". "); else Console.Write(" "); } Console.WriteLine(); } } // Показать путь с векторами скорости public void ShowPath(List 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; Console.WriteLine($"Шаг {i}: Позиция={path[i]}, Скорость={velocity}, Ускорение={acceleration}"); prevVelocity = velocity; } } } class Program { static void Main(string[] args) { Console.WriteLine("=== Гонки на бумаге ===\n"); // Создаем поле 15x15 int width = 42; int height = 42; // Стартовая позиция var start = new Point(1, 1); // Чекпоинты (нужно посетить все в любом порядке) var checkpoints = new Dictionary { { 1, new Point(5, 5) }, { 2, new Point(10, 10) }, { 3, new Point(12, 3) } }; // Препятствия var obstacles = new HashSet(); // Горизонтальная стена for (int x = 3; x <= 8; x++) obstacles.Add(new Point(x, 7)); // Вертикальная стена for (int y = 2; y <= 6; y++) obstacles.Add(new Point(8, y)); // Создаем трек var track = new RaceTrack(width, height, start, checkpoints, obstacles); Console.WriteLine("Начальное поле:"); Console.WriteLine("S - старт, 1,2,3 - чекпоинты, # - препятствия\n"); track.Visualize(); Console.WriteLine("\nПоиск решения...\n"); var solution = track.FindSolution(); if (solution != null) { Console.WriteLine("\n=== РЕШЕНИЕ НАЙДЕНО ===\n"); track.Visualize(solution); track.ShowPath(solution); } else { Console.WriteLine("\nРешение не найдено!"); } } } }