protected void CalculateLowerBoundMaps() { // Create the pre-calculated lower bound data structures. // Create work arrays for matching lower bound function. boxTarget = new int[boxes]; targetBox = new int[targets]; distance = new int[boxes]; // Create an empty level with path finder for measuring distances. Level emptyLevel = LevelUtils.GetEmptyLevel(originalLevel); PathFinder emptyPathFinder = PathFinder.CreateInstance(emptyLevel, true); // Create the target distance maps and target index map. targetDistanceMaps = new Array2D<int>[targets]; targetIndexMap = new Array2D<int>(level.Height, level.Width); targetIndexMap.SetAll(-1); for (int i = 0; i < targets; i++) { targetDistanceMaps[i] = new Array2D<int>(level.Height, level.Width); targetIndexMap[targetCoordinates[i]] = i; } // For each inside square calculate the sorted list of nearest targets and // the distance to the nearest target. nearestTargetsMap = new Array2D<int[]>(level.Height, level.Width); nearestTargetDistanceMap = new Array2D<int>(level.Height, level.Width); foreach (Coordinate2D boxCoord in level.InsideCoordinates) { List<int> nearestTargets = new List<int>(); for (int j = 0; j < targets; j++) { Coordinate2D targetCoord = targetCoordinates[j]; targetDistanceMaps[j][boxCoord] = emptyPathFinder.FindAndGetDistance(boxCoord, targetCoord); nearestTargets.Add(j); } nearestTargets.Sort(delegate(int index1, int index2) { int distance1 = targetDistanceMaps[index1][boxCoord]; int distance2 = targetDistanceMaps[index2][boxCoord]; return distance1.CompareTo(distance2); }); nearestTargetDistanceMap[boxCoord] = targetDistanceMaps[nearestTargets[0]][boxCoord]; nearestTargetsMap[boxCoord] = nearestTargets.ToArray(); } }
private Level GenerateLevelBlob(State state, int size, Array2D<Cell> designData, Array2D<bool> designFixed, out int designRow, out int designColumn) { // Inflate size for extra cells to be subtracted. int blobSize = size * (density + 100) / 100; // Create larger work array and corresponding fixed map. Array2D<Cell> array = new Array2D<Cell>(2 * size, 2 * size); array.SetAll(Cell.Outside); Array2D<bool> arrayFixed = new Array2D<bool>(array.Height, array.Width); // Adjust design data so that outside or undefined cells are empty. Array2D<Cell> designDataCopy = new Array2D<Cell>(designData); designDataCopy.Replace(Cell.Outside, Cell.Empty); designDataCopy.Replace(Cell.Undefined, Cell.Empty); // Copy design into middle of work array. designRow = size; designColumn = size; designDataCopy.CopyTo(array, designRow + 1, designColumn + 1, 1, 1, designData.Height - 2, designData.Width - 2); designFixed.CopyTo(arrayFixed, designRow + 1, designColumn + 1, 1, 1, designData.Height - 2, designData.Width - 2); // Set intial boundaries. int rowMin = array.Height; int colMin = array.Width; int rowMax = -1; int colMax = -1; int count = 0; foreach (Coordinate2D coord in array.Coordinates) { if (!Level.IsOutside(array[coord])) { rowMin = Math.Min(rowMin, coord.Row); colMin = Math.Min(colMin, coord.Column); rowMax = Math.Max(rowMax, coord.Row); colMax = Math.Max(colMax, coord.Column); count++; } } while (count < blobSize) { // Choose an edge at random. int edge = state.Random.Next(4); int row = 0; int column = 0; int limit = 0; int hIncr = 0; int vIncr = 0; switch (edge) { case 0: row = rowMin - 1; column = colMin + state.Random.Next(colMax - colMin + 1); limit = rowMax - rowMin + 1; vIncr = 1; hIncr = 0; break; case 1: row = rowMax + 1; column = colMin + state.Random.Next(colMax - colMin + 1); limit = rowMax - rowMin + 1; vIncr = -1; hIncr = 0; break; case 2: row = rowMin + state.Random.Next(rowMax - rowMin + 1); column = colMin - 1; limit = colMax - colMin + 1; vIncr = 0; hIncr = 1; break; case 3: row = rowMin + state.Random.Next(rowMax - rowMin + 1); column = colMax + 1; limit = colMax - colMin + 1; vIncr = 0; hIncr = -1; break; } // Search along a line until we hit a empty or fixed cell. bool found = false; for (int i = 0; i < limit; i++) { if (array[row + vIncr, column + hIncr] != Cell.Outside || arrayFixed[row + vIncr, column + hIncr]) { if (!arrayFixed[row, column]) { found = true; } break; } row += vIncr; column += hIncr; } // If we didn't find anything, try again. if (!found) { continue; } // Don't allow the level to grow outside the array. if (row < 1 || row >= array.Height - 1 || column < 1 || column >= array.Width - 1) { continue; } // Add the new square and update the boundaries. array[row, column] = Cell.Empty; rowMin = Math.Min(rowMin, row); colMin = Math.Min(colMin, column); rowMax = Math.Max(rowMax, row); colMax = Math.Max(colMax, column); count++; } int attemptsLeft = 2 * (count - size); while (count > size && attemptsLeft > 0) { // Choose a new square at random. int row = rowMin + state.Random.Next(rowMax - rowMin + 1); int column = colMin + state.Random.Next(colMax - colMin + 1); Coordinate2D coord = new Coordinate2D(row, column); if (!array.IsValid(coord)) { continue; } // Avoid existing walls and outside areas. if (!Level.IsInside(array[coord])) { continue; } // We might get into an infinite loop. attemptsLeft--; // Avoid fixed cells. if (arrayFixed[coord]) { continue; } // Avoid cells on the perimeter. bool isAdjacent = false; foreach (Coordinate2D neighbor in coord.EightNeighbors) { if (array[neighbor] == Cell.Outside) { isAdjacent = true; break; } } if (isAdjacent) { continue; } // Remove the cell. array[coord] = Cell.Wall; count--; } // Extract the constructed level. Array2D<Cell> subarray = array.GetSubarray(rowMin - 1, colMin - 1, rowMax - rowMin + 3, colMax - colMin + 3); subarray.Replace(Cell.Outside, Cell.Wall); Level level = new Level(subarray); // Adjust design coordinate. designRow -= rowMin - 1; designColumn -= colMin - 1; return level; }
private void UnsafeGenerateOneLevel(State state) { // Randomly select a supplied design. state.CurrentDesign = state.Random.Next(designCount); Array2D<Cell> designData = designInfo[state.CurrentDesign].Data; Array2D<bool> designFixed = designInfo[state.CurrentDesign].Fixed; // Choose a size at random between the minimum and maximum limits. int size = minSize + state.Random.Next(maxSize - minSize + 1); state.CurrentSize = size; int designRow; int designColumn; Level level = null; if (useEntireLevel) { designRow = 0; designColumn = 0; level = new Level(new Array2D<Cell>(designData)); } else { level = GenerateLevel(state, size, designData, designFixed, out designRow, out designColumn); } // Make sure will have something to solve. if (designInfo[state.CurrentDesign].Boxes == 0 && boxes == 0) { state.Log.DebugPrint("level generated with no boxes"); return; } // Add islands not included in design. for (int i = 0; i < islands; i++) { AddIsland(state, level); } // Add boxes not included in design. if (designInfo[state.CurrentDesign].Boxes < boxes) { int boxesToAdd = boxes - designInfo[state.CurrentDesign].Boxes; int displacedBoxes = boxesToAdd >= displaced ? displaced : boxesToAdd; int inplaceBoxes = boxesToAdd - displacedBoxes; for (int i = 0; i < displacedBoxes; i++) { AddDisplacedBox(state, level); } for (int i = 0; i < inplaceBoxes; i++) { AddInplaceBox(state, level); } } if (verbose >= 2) { state.Log.DebugPrint("design = {0}, size = {1}, algorithm = {2}", state.CurrentDesign, state.CurrentSize, state.CurrentAlgorithm); state.Log.DebugPrint(level.AsText); } // Create a map of squares that have already been tried // as the starting sokoban coordinate, initialized to false. Array2D<bool> tried = new Array2D<bool>(level.Height, level.Width); Coordinate2D sokobanCoord = designInfo[state.CurrentDesign].SokobanCoordinate; if (!sokobanCoord.IsUndefined) { // Only try specified sokoban coordinate. tried.SetAll(true); tried[sokobanCoord.Row + designRow, sokobanCoord.Column + designColumn] = false; } // For all accessible squares. PathFinder finder = PathFinder.CreateInstance(level); while (true) { // Find an untried starting square. bool foundSquare = false; foreach (Coordinate2D coord in level.InsideCoordinates) { if (level.IsEmpty(coord) && !tried[coord]) { sokobanCoord = coord; foundSquare = true; break; } } if (!foundSquare) { break; } // Put sokoban on untried empty square. level.AddSokoban(sokobanCoord); // Find all squares accessible from this one. finder.Find(); // Mark all accessible squares as tried. foreach (Coordinate2D coord in finder.AccessibleCoordinates) { tried[coord] = true; } MoveList moveList = FastSolve(state, level); if (moveList != null) { int pushes = LevelUtils.SolutionPushes(moveList); if (verbose >= 1) { state.Log.DebugPrint("raw: moves = {0}, pushes = {1}", moveList.Count, pushes); } if (pushes > pushLimit) { // First clean up the level which might have a large number // of excess squares that would stress the final solver. Level cleanLevel = LevelUtils.CleanLevel(level, moveList); // Now get a clean solution using move optimization. MoveList cleanMoveList = FinalSolve(state, cleanLevel); // Although this does have a solution, the solver // could fail for other reasons (out of memory, etc). if (cleanMoveList != null) { // Finally check whether there are any squares we can remove. AddSmallestLevel(state, cleanLevel, cleanMoveList); } } } else { if (verbose >= 2) { state.Log.DebugPrint("no solution"); } } level.RemoveSokoban(); } }
private Level GenerateLevelBuckshot(State state, int size, Array2D<Cell> designData, Array2D<bool> designFixed, out int designRow, out int designColumn) { // Create larger work array and corresponding fixed map. Array2D<Cell> array = new Array2D<Cell>(2 * size + designData.Height, 2 * size + designData.Width); array.SetAll(Cell.Wall); Array2D<bool> arrayFixed = new Array2D<bool>(array.Height, array.Width); // Adjust design data so that undefined cells are walls. Array2D<Cell> designDataCopy = new Array2D<Cell>(designData); designDataCopy.Replace(Cell.Undefined, Cell.Wall); // Copy design into middle of work array. designRow = size; designColumn = size; designDataCopy.CopyTo(array, designRow, designColumn, 0, 0, designData.Height, designData.Width); designFixed.CopyTo(arrayFixed, designRow, designColumn, 0, 0, designData.Height, designData.Width); // Set intial boundaries. int rowMin = array.Height; int colMin = array.Width; int rowMax = -1; int colMax = -1; int count = 0; foreach (Coordinate2D coord in array.Coordinates) { if (!Level.IsWall(array[coord])) { rowMin = Math.Min(rowMin, coord.Row); colMin = Math.Min(colMin, coord.Column); rowMax = Math.Max(rowMax, coord.Row); colMax = Math.Max(colMax, coord.Column); count++; } } while (count < size) { // Choose a new square at random. int row = rowMin - growth + state.Random.Next(rowMax - rowMin + 1 + 2 * growth); int column = colMin - growth + state.Random.Next(colMax - colMin + 1 + 2 * growth); Coordinate2D coord = new Coordinate2D(row, column); if (!array.IsValid(coord)) { continue; } // Avoid fixed cells. if (arrayFixed[coord]) { continue; } // Avoid existing squares. if (!Level.IsWall(array[coord])) { continue; } // Ensure the new square is adjacent to an existing square. bool isAdjacent = false; foreach (Coordinate2D neighbor in coord.FourNeighbors) { if (!Level.IsWall(array[neighbor])) { isAdjacent = true; break; } } if (!isAdjacent) { continue; } // Add the new square and update the boundaries. array[coord] = Cell.Empty; rowMin = Math.Min(rowMin, row); colMin = Math.Min(colMin, column); rowMax = Math.Max(rowMax, row); colMax = Math.Max(colMax, column); count++; } // Extract the constructed level. Array2D<Cell> subarray = array.GetSubarray(rowMin - 1, colMin - 1, rowMax - rowMin + 3, colMax - colMin + 3); Level level = new Level(subarray); // Adjust design coordinate. designRow -= rowMin - 1; designColumn -= colMin - 1; return level; }
private static Level ExpandAndReplace(Level level, int rowOffset, int columnOffset, Level replacement) { // Compute the overlap. int row0 = Math.Min(0, rowOffset - 1); int column0 = Math.Min(0, columnOffset - 1); int row1 = Math.Max(level.Height, replacement.Height - 2 + rowOffset); int column1 = Math.Max(level.Width, replacement.Width - 2 + columnOffset); int height = row1 - row0; int width = column1 - column0; // Expand the original level. Array2D<Cell> newData = new Array2D<Cell>(height, width); newData.SetAll(Cell.Wall); level.Data.CopyTo(newData, -row0, -column0, 0, 0, level.Height, level.Width); Level newLevel = new Level(newData); // Retry the replacement. return Replace(newLevel, rowOffset - row0, columnOffset - column0, replacement); }
private static Level TryNormalizeTunnel(Level level, Coordinate2D tunnelCoord, Direction direction) { // Determine the offsets of the parallel direction. Coordinate2D parallel = direction; // If the upper half is closed off it's not a tunnel. if (level.IsWall(tunnelCoord - parallel)) { return null; } // Empty out the level. Level newLevel = LevelUtils.GetEmptyLevel(level); // Add a wall to separate the level into two halves. newLevel[tunnelCoord] = Cell.Wall; // Find all cells accessible from the upper half. PathFinder finder = PathFinder.CreateInstance(newLevel); finder.Find(tunnelCoord - parallel); // If the lower half is accessible from the upper half we can't normalize. if (finder.IsAccessible(tunnelCoord + parallel)) { return null; } // Create a map of the two regions, initialized to zero. Array2D<int> regionMap = new Array2D<int>(newLevel.Height, newLevel.Width); // Mark first region in the map. foreach (Coordinate2D coord in finder.AccessibleCoordinates) { regionMap[coord] = 1; } // Find all cells accessible from the lower half. finder.Find(tunnelCoord + parallel); // Mark second region in the map. foreach (Coordinate2D coord in finder.AccessibleCoordinates) { regionMap[coord] = 2; } // See if normalization would result in overlap. foreach (Coordinate2D coord in newLevel.Coordinates) { // For all coordinates in region one. if (regionMap[coord] != 1) { continue; } if (coord == tunnelCoord - parallel) { // We will have intentional contact right at the match site. continue; } // Check whether there would be a square in region 1 adjacent to a square in region 2. Coordinate2D newCoord = coord + parallel; foreach (Coordinate2D neighbor in newCoord.FourNeighbors) { if (newLevel.IsValid(neighbor) && regionMap[neighbor] == 2) { // Normalizing would alter the level. return null; } } } // Normalize the level using the region map as our guide. Array2D<Cell> data = new Array2D<Cell>(level.Height, level.Width); data.SetAll(Cell.Wall); foreach (Coordinate2D coord in level.Coordinates) { if (regionMap[coord] == 1) { data[coord] = level[coord]; } else if (regionMap[coord] == 2) { data[coord - parallel] = level[coord]; } } // Construct the level. return new Level(data); }