private static void CheckForCapturedLine(Level level, Array2D<bool> simpleDeadlockMap, Coordinate2D coord, Coordinate2D perpendicular, Coordinate2D parallel) { // Check for straight walls that look like this: // ########### // #.-> # // where the dot is the starting position // and perpendicular is the direction of the wall // and parallel is the direction to search. if (!level.IsWall(coord - parallel)) { return; } bool isCapturedLine = false; for (Coordinate2D c = coord; !level.IsTarget(c); c += parallel) { if (!level.IsWall(c + perpendicular)) { break; } if (level.IsWall(c + parallel)) { isCapturedLine = true; break; } } if (!isCapturedLine) { return; } for (Coordinate2D c = coord; !level.IsWall(c); c += parallel) { simpleDeadlockMap[c] = true; } }
private Coordinate2D GetRandomJustFloorSquareAvoidingPerimeter(State state, Level level) { List<Coordinate2D> coordList = new List<Coordinate2D>(); foreach (Coordinate2D coord in level.InsideCoordinates) { if (level.IsJustFloor(coord)) { bool onPerimeter = false; foreach (Coordinate2D neighbor in coord.FourNeighbors) { if (level.IsWall(neighbor)) { onPerimeter = true; break; } } if (!onPerimeter) { coordList.Add(coord); } } } if (coordList.Count == 0) { return Coordinate2D.Undefined; } return coordList[state.Random.Next(coordList.Count)]; }
public static Level TrimWalls(Level level) { // First make a copy. Level newLevel = new Level(level); // Delete walls that don't contact the interior. foreach (Coordinate2D coord in newLevel.Coordinates) { if (!level.IsWall(coord)) { continue; } bool foundInterior = false; foreach (Coordinate2D neighbor in coord.EightNeighbors) { if (level.IsInside(neighbor)) { foundInterior = true; break; } } if (!foundInterior) { newLevel[coord] = Cell.Empty; } } return newLevel; }
private static void CheckBox(Level level, Array2D<int> noBoxMap, Array2D<bool> simpleDeadlockMap, Coordinate2D coord) { // Check whether we've visited this square before. if (noBoxMap[coord] == 0) { return; } // This square is not a no-box square. noBoxMap[coord] = 0; // Recursively check for other squares that are not no-box squares. foreach (Direction direction in Direction.Directions) { // Get parallel and perpendicular offsets. Coordinate2D parallel = direction; Coordinate2D perpendicular = Coordinate2D.Transpose(parallel); // Check whether we can move the box to the new position. if (level.IsFloor(coord - parallel) && level.IsFloor(coord + parallel)) { Coordinate2D oldBoxCoord = coord; Coordinate2D newBoxCoord = coord + parallel; // Check for the special case of no influence pushes that lead to deadlocks. bool specialCase = false; while (level.IsWall(oldBoxCoord + perpendicular) && level.IsWall(oldBoxCoord - perpendicular) && ((level.IsWall(newBoxCoord + perpendicular) && level.IsWallOrEmpty(newBoxCoord - perpendicular)) || (level.IsWallOrEmpty(newBoxCoord + perpendicular) && level.IsWall(newBoxCoord - perpendicular)))) { if (level.IsTarget(newBoxCoord)) { break; } if (!level.IsFloor(newBoxCoord) || simpleDeadlockMap[newBoxCoord]) { specialCase = true; break; } oldBoxCoord += parallel; newBoxCoord += parallel; } // Otherwise recursively check for more squares. if (!specialCase) { CheckBox(level, noBoxMap, simpleDeadlockMap, coord + parallel); } } } }
public static Level ShrinkToFit(Level level) { // Determine unneeded exterior rows and columns. int rowMin = level.Height; int colMin = level.Width; int rowMax = -1; int colMax = -1; foreach (Coordinate2D coord in level.Coordinates) { if (level.IsWall(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); } } Array2D<Cell> data = level.Data.GetSubarray(rowMin, colMin, rowMax - rowMin + 1, colMax - colMin + 1); return new Level(data); }
public static Array2D<bool> GetIslandMap(Level level) { Array2D<bool> islandMap = new Array2D<bool>(level.Height, level.Width); foreach (Coordinate2D coord in islandMap.Coordinates) { islandMap[coord] = level.IsWall(coord); } islandMap.FloodFill(FindFirstWall(level), true, false); return islandMap; }
public static Array2D<bool> GetCapturedMap(Level level) { // Create a map of cell positions that if occupied correspond // to a deadlock. This is similar to a simple deadlock map // but returns a different result if there are no targets. Array2D<bool> simpleDeadlockMap = new Array2D<bool>(level.Height, level.Width); foreach (Coordinate2D coord in level.InsideCoordinates) { if (level.IsTarget(coord)) { continue; } bool wallUp = level.IsWall(coord + Direction.Up); bool wallDown = level.IsWall(coord + Direction.Down); bool wallLeft = level.IsWall(coord + Direction.Left); bool wallRight = level.IsWall(coord + Direction.Right); if (wallUp && (wallLeft || wallRight)) { simpleDeadlockMap[coord] = true; continue; } if (wallDown && (wallLeft || wallRight)) { simpleDeadlockMap[coord] = true; continue; } } foreach (Coordinate2D coord in level.InsideCoordinates) { if (simpleDeadlockMap[coord]) { CheckForCapturedLine(level, simpleDeadlockMap, coord, Direction.Up, Direction.Right); CheckForCapturedLine(level, simpleDeadlockMap, coord, Direction.Down, Direction.Right); CheckForCapturedLine(level, simpleDeadlockMap, coord, Direction.Left, Direction.Down); CheckForCapturedLine(level, simpleDeadlockMap, coord, Direction.Right, Direction.Down); } } return simpleDeadlockMap; }
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); }
private static Level TryNormalizeLoops(Level level) { // A loop is a tunnel that you cannot push a box through. // Any square that it is impossible or not useful to push // a box to is a no-box square. All the squares in a loop // are no-box squares. A level may have several disconnected // no-box regions. Within one no-box region we can // optimize the path by removing islands that only // lengthen the loop without changing its function. // Finally, a normalized loop only needs to be one square // wide. // Copy the level and get island and no-box maps. bool modifiedLevel = false; Level newLevel = new Level(level); Array2D<bool> islandMap = LevelUtils.GetIslandMap(level); Array2D<int> noBoxMap = LevelUtils.GetNoBoxMap(level); // Any island that only contacts other walls and one or more // no-box squares in the same region can be removed. foreach (Coordinate2D coord in newLevel.Coordinates) { if (islandMap[coord]) { int wallCount = 0; int noBoxCount = 0; int region = 0; foreach (Coordinate2D neighbor in coord.FourNeighbors) { if (newLevel.IsWall(neighbor)) { wallCount++; } else if (noBoxMap[neighbor] != 0) { // Check whether we've encountered any no-box regions. if (region == 0) { region = noBoxMap[neighbor]; } // Only count no-box squares that match the first region. if (noBoxMap[neighbor] == region) { noBoxCount++; } } } if (wallCount + noBoxCount == 4 && noBoxCount >= 1) { newLevel[coord] = Cell.Empty; noBoxMap[coord] = 1; islandMap[coord] = false; modifiedLevel = true; } } } // Make a map of all the inside squares we want to keep, initialized to false. Array2D<bool> keepMap = new Array2D<bool>(newLevel.Height, newLevel.Width); foreach (Coordinate2D coord in newLevel.InsideCoordinates) { if (noBoxMap[coord] != 0) { // Keep all the no-box squares that contact box islands. foreach (Coordinate2D neighbor in coord.EightNeighbors) { if (newLevel.IsValid(neighbor) && islandMap[neighbor]) { keepMap[coord] = true; break; } } // Keep all the no-box squares that contact box squares. foreach (Coordinate2D neighbor in coord.FourNeighbors) { if (newLevel.IsFloor(neighbor) && noBoxMap[neighbor] == 0) { keepMap[coord] = true; break; } } } else { // Keep all the box squares. keepMap[coord] = true; } } // Fill in the no-box squares that we can safely remove. Coordinate2D sokobanCoord = newLevel.SokobanCoordinate; foreach (Coordinate2D coord in newLevel.InsideCoordinates) { if (!keepMap[coord]) { newLevel[coord] = Cell.Wall; modifiedLevel = true; } } // If the sokoban was on one of the cells we didn't keep, // move it to a nearby box square. if (!newLevel.IsSokoban(sokobanCoord)) { Level boxLevel = FillBoxSquaresWithBoxes(level, noBoxMap); PathFinder finder = PathFinder.CreateInstance(boxLevel); finder.Find(sokobanCoord); foreach (Coordinate2D coord in finder.AccessibleCoordinates) { if (newLevel.IsEmpty(coord)) { newLevel[coord] = Cell.Sokoban; break; } } } // Return the new level only if we made changes. if (modifiedLevel) { return newLevel; } return null; }