/// <summary> /// Создание карты. /// </summary> /// <returns> /// Возвращает экземпляр карты. /// </returns> public Task <ISectorMap> CreateAsync(object options) { if (options is null) { throw new System.ArgumentNullException(nameof(options)); } var sectorScheme = (ISectorSubScheme)options; var map = CreateMapInstance(); var edgeHash = new HashSet <string>(); // Генерируем случайные координаты комнат var transitions = MapFactoryHelper.CreateTransitions(sectorScheme); var rooms = _roomGenerator.GenerateRoomsInGrid(sectorScheme.RegionCount, RoomMinSize, sectorScheme.RegionSize, transitions); // Создаём узлы и рёбра комнат _roomGenerator.CreateRoomNodes(map, rooms, edgeHash); // Соединяем комнаты _roomGenerator.BuildRoomCorridors(map, rooms, edgeHash); // Указание регионов карты var regionIdCounter = 1; foreach (var room in rooms) { var passableRoomNodes = room.Nodes.Where(x => !x.IsObstacle); var region = new MapRegion(regionIdCounter, passableRoomNodes.Cast <IMapNode>().ToArray()); regionIdCounter++; map.Regions.Add(region); if (room.IsStart) { region.IsStart = true; continue; } if (room.Transitions?.Any() == true) { region.ExitNodes = (from regionNode in region.Nodes where map.Transitions.Keys.Contains(regionNode) select regionNode).ToArray(); continue; } } return(Task.FromResult(map)); }
private static bool CheckMapPassable(IEnumerable <OffsetCoords> currentCoords, OffsetCoords targetCoords) { var matrix = new Matrix <bool>(2002, 2002); foreach (var coords in currentCoords) { try { var x = coords.X; var y = coords.Y; matrix.Items[x, y] = true; } catch (IndexOutOfRangeException) { // Do nothing. The solution by using matrix is code smell. // There is the catch block just for debug. } } // Закрываем проверяемый узел matrix.Items[targetCoords.X, targetCoords.Y] = false; // Не выбираем првоеряемую координату, как стартовую, потому что // она уже закрыта. Заливка от неё не пройдёт. var availableStartPoints = currentCoords.Where(x => x != targetCoords).ToArray(); if (!availableStartPoints.Any()) { // Если нет доступных координат для старта, // значит стартовая координата была единственной. // Её нельзя закрывать препятсвием. return(false); } var startPoint = availableStartPoints.First(x => MapFactoryHelper.IsAvailableFor(matrix, x)); var floodPoints = HexBinaryFiller.FloodFill(matrix, startPoint); foreach (var point in floodPoints) { matrix.Items[point.X, point.Y] = false; } foreach (var node in currentCoords) { var x = node.X; var y = node.Y; if (matrix.Items[x, y]) { return(false); } } return(true); }
public async Task <ISector> GenerateAsync(ISectorNode sectorNode) { var transitions = MapFactoryHelper.CreateTransitions(sectorNode).ToArray(); var map = await SquareMapFactory.CreateAsync(transitions.Count() + 1); CreateTransitions(transitions, map); var locationScheme = sectorNode.Biome.LocationScheme; var sector = _sectorFactory.Create(map, locationScheme); return(sector); }
public IEnumerable <RegionDraft> Generate(ref Matrix <bool> matrix, int fillProbability, int totalIterations) { InitiateMatrix(matrix, fillProbability); matrix = SimulateCellularAutomaton(matrix, totalIterations); var resizedMatrix = MapFactoryHelper.ResizeMatrixTo7(matrix); var matrixWithMargins = resizedMatrix.CreateMatrixWithMargins(2, 2); var draftRegions = RegionFinder.FindPassableRegionsFor(matrixWithMargins).ToArray(); matrix = matrixWithMargins; return(draftRegions); }
/// <summary> /// Find all passable (true) regions. /// </summary> public static IEnumerable <RegionDraft> FindPassableRegionsFor(Matrix <bool> matrix) { var openNodesEnum = AddAllPassableNodesToOpenList(matrix); var openNodes = openNodesEnum.ToList(); var regions = new List <RegionDraft>(); while (openNodes.Any()) { var openNode = openNodes.First(x => MapFactoryHelper.IsAvailableFor7(matrix, x)); var regionCoords = FloodFillRegions(matrix, openNode); var region = new RegionDraft(regionCoords.ToArray()); openNodes.RemoveAll(x => region.Contains(x)); regions.Add(region); } return(regions); }
/// <inheritdoc /> public async Task <ISector> GenerateAsync(ISectorNode sectorNode) { var transitions = MapFactoryHelper.CreateTransitions(sectorNode); var sectorFactoryOptions = new SectorMapFactoryOptions(sectorNode.SectorScheme.MapGeneratorOptions, transitions); var map = await _mapFactory.CreateAsync(sectorFactoryOptions).ConfigureAwait(false); var actorManager = new ActorManager(); var staticObjectManager = new StaticObjectManager(); var sector = new Sector(map, actorManager, staticObjectManager, _dropResolver, _schemeService, _equipmentDurableService); return(sector); }
public void ResizeMatrixTo7Test() { // ARRANGE var matrix = new Matrix <bool>(1, 1); matrix[0, 0] = true; // ACT var resizedMatrix = MapFactoryHelper.ResizeMatrixTo7(matrix); // ASSERT resizedMatrix[1, 1].Should().BeTrue(); var n = HexHelper.GetNeighbors(1, 1); foreach (var c in n) { resizedMatrix[c.X, c.Y].Should().BeTrue(); } }
/// <summary> /// Создаёт карту сектора. /// </summary> /// <param name="options">Настройки генерации. /// Должны быть типа ISectorSubScheme с заданным значением MapGeneratorOptions. /// Значение MapGeneratorOptions должно быть типа ISectorCellularAutomataMapFactoryOptionsSubScheme.</param> /// <returns></returns> public Task <ISectorMap> CreateAsync(object options) { if (options is null) { throw new ArgumentNullException(nameof(options)); } var sectorScheme = (ISectorSubScheme)options; var transitions = MapFactoryHelper.CreateTransitions(sectorScheme); var cellularAutomatonOptions = (ISectorCellularAutomataMapFactoryOptionsSubScheme)sectorScheme.MapGeneratorOptions; if (cellularAutomatonOptions == null) { throw new ArgumentException($"Для {nameof(options)} не задано {nameof(ISectorSubScheme.MapGeneratorOptions)} равно null."); } var matrixWidth = cellularAutomatonOptions.MapWidth; var matrixHeight = cellularAutomatonOptions.MapHeight; var chanceToStartAlive = cellularAutomatonOptions.ChanceToStartAlive; var targetRegionDraftCount = transitions.Count() + 1; var createResult = CreateInner( targetRegionDraftCount, chanceToStartAlive, matrixWidth, matrixHeight); var matrix = createResult.Matrix; var draftRegions = createResult.Regions; var map = CreateSectorMap(matrix, draftRegions, transitions); return(Task.FromResult(map)); }
//TODO Придумать название метода получше private CreateMatrixResult CreateInner(int targetRegionDraftCount, int chanceToStartAlive, int matrixWidth, int matrixHeight) { var matrix = new Matrix <bool>(matrixWidth, matrixHeight); for (var retry = 0; retry < RETRY_LIMIT; retry++) { InitStartAliveMatrix(matrix, chanceToStartAlive); // Несколько шагов симуляции for (var i = 0; i < SIMULATION_COUNT; i++) { var newMap = DoSimulationStep(matrix); matrix = new Matrix <bool>(newMap.Items, matrix.Width, matrix.Height); } var resizedMatrix = MapFactoryHelper.ResizeMatrixTo7(matrix); var matrixWithMargins = resizedMatrix.CreateMatrixWithMargins(2, 2); RegionDraft[] draftRegions; try { draftRegions = MakeUnitedRegions(matrixWithMargins); } catch (CellularAutomatonException) { // Это означает, что при текущих стартовых данных невозможно создать подходящую карту. // Запускаем следующую итерацю. continue; } // Обрабатываем ситуацию, когда на карте тегионов меньше, чем переходов. // На карте должно быть минимум столько регионов, сколько переходов. // +1 - это регион старта. if (draftRegions.Length < targetRegionDraftCount) { try { var splittedDraftRegions = SplitRegionsForTransitions(draftRegions, targetRegionDraftCount); // Разделение успешно выполнено. // Пропускаем карту дальше. var result = new CreateMatrixResult(matrixWithMargins, splittedDraftRegions); return(result); } catch (CellularAutomatonException) { // Это означает, что при текущих стартовых данных невозможно создать подходящую карту. // Запускаем следующую итерацю. continue; } } else { var result = new CreateMatrixResult(matrixWithMargins, draftRegions); return(result); } } // Если цикл закончился, значит вышел лимит попыток. throw new InvalidOperationException("Не удалось создать карту за предельное число попыток."); }
private static RegionDraft[] MakeUnitedRegions(Matrix <bool> matrix) { // Формирование регионов. // Регионы, кроме дальнейшего размещения игровых предметов, // в рамках этой генерации будут служить для обнаружения // изолированных регионов. // В секторе не должно быть изолированых регионов, поэтому // дальше все регионы объединяются в единый граф. var openNodes = new List <OffsetCoords>(); AddAllPassableNodesToOpenList(matrix, openNodes); if (!openNodes.Any()) { throw new CellularAutomatonException("Ни одна из клеток не выжила."); } // Разбиваем все проходимые (true) клетки на регионы // через заливку. var regions = new List <RegionDraft>(); while (openNodes.Any()) { var openNode = openNodes.First(x => MapFactoryHelper.IsAvailableFor7(matrix, x)); var regionCoords = FloodFillRegions(matrix, openNode); var region = new RegionDraft(regionCoords.ToArray()); openNodes.RemoveAll(x => region.Contains(x)); regions.Add(region); } // Соединяем все регионы в единый граф. var openRegions = new List <RegionDraft>(regions); var unitedRegions = new List <RegionDraft>(); var startRegion = openRegions[0]; openRegions.RemoveAt(0); unitedRegions.Add(startRegion); while (openRegions.Any()) { var unitedRegionCoords = unitedRegions.SelectMany(x => x.Coords).ToArray(); // Ищем две самые ближние точки между объединённым регионом и // и всеми открытыми регионами. FindClosestNodesBetweenOpenAndUnited( openRegions, unitedRegionCoords, out OffsetCoords? currentOpenRegionCoord, out OffsetCoords? currentUnitedRegionCoord, out RegionDraft nearbyOpenRegion); // Если координаты, которые нужно соединить, найдены, // то прорываем тоннель. if (nearbyOpenRegion != null && currentOpenRegionCoord != null && currentUnitedRegionCoord != null) { var openCubeCoord = HexHelper.ConvertToCube(currentOpenRegionCoord.Value); var unitedCubeCoord = HexHelper.ConvertToCube(currentUnitedRegionCoord.Value); DrawLineBetweenNodes(matrix, openCubeCoord, unitedCubeCoord); openRegions.Remove(nearbyOpenRegion); unitedRegions.Add(nearbyOpenRegion); } } return(regions.ToArray()); }