public static Level Replace(Level level, int rowOffset, int columnOffset, Level replacement) { Level newLevel = new Level(level); bool lostSokoban = false; for (int row = 1; row < replacement.Height - 1; row++) { for (int column = 1; column < replacement.Width - 1; column++) { if (replacement[row, column] != Cell.Undefined) { int rowTarget = row - 1 + rowOffset; int columnTarget = column - 1 + columnOffset; if (!level.IsValid(rowTarget, columnTarget)) { return ExpandAndReplace(level, rowOffset, columnOffset, replacement); } if (level.IsSokoban(rowTarget, columnTarget) && !replacement.IsSokoban(row, column)) { lostSokoban = true; } newLevel[rowTarget, columnTarget] = replacement[row, column]; } } } if (lostSokoban) { // Find an empty cell to relocate the sokoban to. foreach (Coordinate2D coord in replacement.Coordinates) { if (replacement[coord] == Cell.Empty) { newLevel.MoveSokoban(coord.Row - 1 + rowOffset, coord.Column - 1 + columnOffset); return newLevel; } } } return newLevel; }
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; }