public void ProcessMap(Map <T> map, DungeonConfiguration configuration, IRandomizer randomizer) { var cellsToRemove = (int)(map.Width * map.Height * configuration.Sparseness); while (cellsToRemove != 0) { //Look at every cell in the maze grid. If the given cell contains a corridor that exits the cell in only one direction //"erase" that cell by removing the corridor var changedCells = new HashSet <T>(); var deadEndCells = map.AllCells.Where(cell => cell.Sides.Values.Count(side => side) == 1).ToList(); if (!deadEndCells.Any()) { break; } foreach (var deadEndCell in deadEndCells) { deadEndCell.IsOpen = false; var openDirection = deadEndCell.Sides.First(pair => pair.Value).Key; deadEndCell.Sides[openDirection] = false; var oppositeCell = map.GetAdjacentCell(deadEndCell, openDirection); oppositeCell.Sides[openDirection.Opposite()] = false; changedCells.Add(deadEndCell); changedCells.Add(oppositeCell); cellsToRemove--; if (cellsToRemove == 0) { break; } } //Repeat step #1 sparseness times } }
/// <summary> /// Generate a map according the configuration received. /// </summary> /// <param name="config">The configuration used to generate the map.</param> /// <param name="seed">A seed to be used for the generation. If null a random seed will be generated.</param> /// <returns>The generated map.</returns> public virtual Map <T> Generate(DungeonConfiguration config, int?seed = null) { var randomizer = new Randomizer(); if (!seed.HasValue) { seed = Guid.NewGuid().GetHashCode(); } randomizer.SetSeed(seed.Value); var halfHeight = config.Height / 2; var halfWidth = config.Width / 2; var map = new Map <BinaryCell>(halfWidth, halfHeight); //pre processing foreach (var preProcessor in mPreProcessors) { preProcessor.ProcessMap(map, config, randomizer); } //double map var postMap = mMapConverter.ConvertMap(map, config, randomizer); //post processing foreach (var postProcessor in mPostProcessors) { postProcessor.ProcessMap(postMap, config, randomizer); } return(postMap); }
/// <summary> /// Generates a map on a different thread. /// </summary> /// <param name="callback">Will be called when the generation is complete (on a different thread).</param> /// <param name="config">The configuration used to generate the map.</param> /// <param name="seed">A seed to be used for the generation. If null a random seed will be generated.</param> public void BeginGenerate(Action <Map <T> > callback, DungeonConfiguration config, int?seed = null) { new Thread(() => { var map = Generate(config, seed); callback(map); }).Start(); }
public void ProcessMap(Map <T> map, DungeonConfiguration configuration, IRandomizer randomizer) { var isolatedRooms = map.Rooms.Where(room => map.GetCellsAdjacentToRoom(room).All(cell => cell.Terrain == TerrainType.Rock)).ToList(); foreach (var room in map.Rooms) { if (isolatedRooms.Contains(room)) { ConnectRoom(map, randomizer, room, isolatedRooms); } //place doors foreach (var cell in map.GetCellsAdjacentToRoom(room) .Where(cell => cell.Terrain == TerrainType.Floor && map.GetAllAdjacentCells(cell).All(c => c.Terrain != TerrainType.Door))) { //don't place a door if it leads to nowhere if (map.GetAllAdjacentCells(cell).Count(c => c.Terrain == TerrainType.Floor) == 1) { continue; } cell.Terrain = TerrainType.Door; } } }
public void ProcessMap(Map <T> map, DungeonConfiguration configuration, IRandomizer randomizer) { var deadends = map.AllCells.Where(cell => cell.Sides.Values.Count(type => type) == 1).ToList(); foreach (var cell in deadends) { if (randomizer.GetRandomDouble() > configuration.ChanceToRemoveDeadends) { continue; } var currentCell = cell; var previousCell = map.GetAdjacentCell(cell, cell.Sides.First(pair => pair.Value).Key); var connected = false; while (!connected) { var direction = GetRandomValidDirection(map, currentCell, previousCell, randomizer); if (!direction.HasValue) { break; } var adjacentCell = map.GetAdjacentCell(currentCell, direction.Value); connected = adjacentCell.IsOpen; adjacentCell.IsOpen = true; currentCell.Sides[direction.Value] = adjacentCell.Sides[direction.Value.Opposite()] = true; previousCell = currentCell; currentCell = adjacentCell; } } }
private static HashSet <Size> GetAllPossibleRoomSizes(DungeonConfiguration configuration) { var sizes = new HashSet <Size>(); for (int i = configuration.MinRoomHeight; i <= configuration.MaxRoomHeight; i++) { for (int j = configuration.MinRoomWidth; j <= configuration.MaxRoomWidth; j++) { sizes.Add(new Size(j, i)); } } return(sizes); }
public void ProcessMap(Map <T> map, DungeonConfiguration configuration, IRandomizer randomizer) { //Start with a rectangular grid, x units wide and y units tall. Mark each cell in the grid unvisited var visitedCells = new HashSet <T>(); var visitedValidCells = new HashSet <T>(); // var deadEndCells = new HashSet<T>(); Direction?previousDirection = null; //Pick a random cell in the grid and mark it visited. This is the current cell. var currentCell = randomizer.GetRandomCell(map); currentCell.IsOpen = true; while (visitedCells.Count < map.Width * map.Height) { var oldCell = currentCell; var changed = false; visitedCells.Add(currentCell); visitedValidCells.Add(currentCell); //From the current cell, pick a random direction (north, south, east, or west). //If (1) there is no cell adjacent to the current cell in that direction, or (2) if //the adjacent cell in that direction has been visited, then that direction //is invalid, and you must pick a different random direction. var direction = GetRandomValidDirection(map, currentCell, visitedCells, configuration.Randomness, previousDirection, randomizer); if (direction.HasValue) { //Let's call the cell in the chosen direction C. Create a corridor between the //current cell and C, and then make C the current cell. Mark C visited. changed = !currentCell.Sides[direction.Value]; currentCell = map.GetAdjacentCell(currentCell, direction.Value); currentCell.Sides[direction.Value.Opposite()] = oldCell.Sides[direction.Value] = true; previousDirection = direction; } else { //If all directions are invalid, pick a different random visited cell in the grid and start this step over again. // deadEndCells.Add(currentCell); visitedValidCells.Remove(currentCell); currentCell = randomizer.GetRandomItem(visitedValidCells); } if (currentCell.IsOpen && !changed) { continue; } currentCell.IsOpen = true; //Repeat until all cells in the grid have been visited. } }
private void GenerateAndAssert( Func <DungeonConfigurationGenerator <Cell>, DungeonConfigurationGenerator <Cell> > method, Action <DungeonConfiguration> assertMethod) { DungeonConfiguration targetConfiguration = null; var fakeGenerator = A.Fake <DungeonGenerator <Cell> >(); A.CallTo(() => fakeGenerator.Generate(null, null)) .WithAnyArguments() .Invokes(callObject => targetConfiguration = callObject.Arguments[0] as DungeonConfiguration); var configGenerator = new DungeonConfigurationGenerator <Cell>(fakeGenerator); method(configGenerator).Now(); assertMethod(targetConfiguration); }
public Map <TPost> ConvertMap(Map <TPre> map, DungeonConfiguration configuration, IRandomizer randomizer) { var oldCells = map.AllCells.ToList(); var newMap = new Map <TPost>(map.Width * 2 + 1, map.Height * 2 + 1); foreach (var oldCell in oldCells.Where(cell => cell.IsOpen)) { var newCell = newMap.GetCell(oldCell.Row * 2 + 1, oldCell.Column * 2 + 1); newCell.Terrain = TerrainType.Floor; foreach (var kvp in oldCell.Sides.Where(pair => pair.Value)) { var adjacentCell = newMap.GetAdjacentCell(newCell, kvp.Key); adjacentCell.Terrain = TerrainType.Floor; } } return(newMap); }
internal Tuple <Map <T>, Dictionary <string, double> > GenerateAndMeasure(DungeonConfiguration config) { var randomizer = new Randomizer(); var seed = Guid.NewGuid().GetHashCode(); randomizer.SetSeed(seed); var halfHeight = config.Height / 2; var halfWidth = config.Width / 2; var map = new Map <BinaryCell>(halfWidth, halfHeight); var results = new Dictionary <string, double>(); DateTime start = DateTime.Now; var totalStart = DateTime.Now; //pre processing foreach (var preProcessor in mPreProcessors) { start = DateTime.Now; preProcessor.ProcessMap(map, config, randomizer); results[preProcessor.GetType().Name] = DateTime.Now.Subtract(start).TotalSeconds; } //double map start = DateTime.Now; var postMap = mMapConverter.ConvertMap(map, config, randomizer); results[mMapConverter.GetType().Name] = DateTime.Now.Subtract(start).TotalSeconds; //post processing foreach (var postProcessor in mPostProcessors) { start = DateTime.Now; postProcessor.ProcessMap(postMap, config, randomizer); results[postProcessor.GetType().Name] = DateTime.Now.Subtract(start).TotalSeconds; } results["Total"] = DateTime.Now.Subtract(totalStart).TotalSeconds; return(new Tuple <Map <T>, Dictionary <string, double> >(postMap, results)); }
public void ProcessMap(Map <T> map, DungeonConfiguration configuration, IRandomizer randomizer) { var validSizes = GetAllPossibleRoomSizes(configuration); for (var i = 0; i < configuration.RoomCount; i++) { //Generate a room such that Wmin <= Rw <= Wmax and Hmin <= Rh <= Hmax. var room = CreateRoom(randomizer, validSizes); if (room == null) { break; } var visitedCells = new HashSet <T>(); var unvisitedCells = new HashSet <T>(map.AllCells); var roomPlaced = false; while (visitedCells.Count < map.Height * map.Width) { //get a random cell var cell = randomizer.GetRandomItem(unvisitedCells); visitedCells.Add(cell); unvisitedCells.Remove(cell); //place the room room.Row = cell.Row; room.Column = cell.Column; if (room.Column <= 0 || room.Right >= map.Width || room.Row <= 0 || room.Bottom >= map.Height) { continue; //out of bounds } var cells = map.GetRoomCells(room).ToList(); //don't place room where it is overlapping another room if (cells.Any(c => map.IsLocationInRoom(c.Row, c.Column))) { continue; } //don't place room where it is adjacent to another room if (map.GetCellsAdjacentToRoom(room).Any(c => map.IsLocationInRoom(c.Row, c.Column))) { continue; } //corners are rock if (!AreAllCornerCellsRocks(map, room)) { continue; //NW corner } //all corridors leading into room can become doors (are isolated) if (!CanAllCorridorsLeadingToRoomBeDoors(map, room)) { continue; } PlaceRoom(map, room); roomPlaced = true; break; } if (!roomPlaced) { validSizes.Remove(room.Size); } } }