private static int TestSimplePuzzle() { SokobanMap map = new SokobanMap(); map.setFromStrings(new string[] { "~~~###~~~~~", "~~##.#~####", "~##..###..#", "##.X......#", "#...PX.#..#", "###.X###..#", "~~#..#OO..#", "~##.##O#.##", "~#......##~", "~#.....##~~", "~#######~~~" }); PuzzleMap pMap = new PuzzleMap(null); pMap.Map = map; SolverAPI api = new SolverAPI(); List<INode<SolverNode>> results = api.Solve(pMap); if (results == null || results.Count== 0) { Console.WriteLine("No Solutions found."); return -1; } return 0; }
public void TestRoom() { string[] puzzle = new string[] { "~~###########", "~##.....#..P#", "###.X.XX#...#", "#.##X....XX.#", "#..#..X.#...#", "######.######", "#OO.OOX.#$##~", "#.OO....###~~", "#..OO#####~~~", "#########~~~~" }; SokobanMap map = new SokobanMap(); map.SetFromStrings(puzzle); PuzzleMap pMap = new PuzzleMap((Puzzle)null); pMap.Map = map; SolverController solver = new SolverController(pMap); solver.Init(); }
public void FloodCrateMoveMap() { SokobanMap map = new SokobanMap(); map.setFromStrings(new string[] { "~~~###~~~~~", "~~##.#~####", "~##..###..#", "##.X......#", "#...PX.#..#", "###.X###..#", "~~#..#OO..#", "~##.##O#.##", "~#......##~", "~#.....##~~", "~#######~~~" }); Bitmap result = CrateAnalysis.BuildCrateMoveMap(map, new VectorInt(3,3)); Assert.IsNotNull(result); Debug.WriteLine(map.ToString()); Debug.WriteLine(result.ToString()); Debug.WriteLine("done."); }
/// <summary> /// Copy Constructor /// </summary> /// <param name="copy"></param> public SokobanMap(SokobanMap copy) { map = new CellStates[copy.Size.X, copy.Size.Y]; for (int px = 0; px < Size.X; px++) for (int py = 0; py < Size.Y; py++) this[new VectorInt(px, py)] = copy[new VectorInt(px, py)]; }
public PuzzleMap(Puzzle owner) { puzzle = owner; map = new SokobanMap(); solutions = new List<Solution>(); isMasterMap = null; }
/// <summary> /// Strong Contruction /// </summary> /// <param name="map">Map to solve</param> public SolverController(SokobanMap map) { if (map == null) throw new ArgumentNullException("map"); this.settings = new Settings(); this.state = States.NotStarted; this.map = map; debugReport = new SolverReport(); debugReport.AppendHeading(1, "SokoSolve | Solver Debug Report v{0}", ProgramVersion.VersionString); debugReport.Append("Creating Statistics"); stats = new SolverStats(this); debugReport.Append("Creating Exit Conditions"); exitConditions = new ItteratorExitConditions(); // TODO: This should be configured, perhaps via a factory pattern debugReport.Append("Creating Forward Strategy"); strategy = new SolverStrategy(this); debugReport.Append("Creating Forward Evaluator"); evaluator = new Evaluator<SolverNode>(true); debugReport.Append("Creating Reverse Strategy"); reverseStrategy = new ReverseStrategy(this); debugReport.Append("Creating Reverse Evaluator"); reverseEvaluator = new Evaluator<SolverNode>(true); }
public PuzzleMap(PuzzleMap clone) { puzzle = clone.puzzle; map = new SokobanMap(clone.map); solutions = new List<Solution>(clone.solutions); details = new GenericDescription(clone.details); rating = clone.rating; mapID = clone.mapID; isMasterMap = clone.IsMasterMap; }
public static IBitmap GenerateFloorMap(SokobanMap Map) { Bitmap boundry = Map.ToBitmap(CellStates.Floor); boundry = boundry.BitwiseOR(Map.ToBitmap(CellStates.FloorCrate)); boundry = boundry.BitwiseOR(Map.ToBitmap(CellStates.FloorGoal)); boundry = boundry.BitwiseOR(Map.ToBitmap(CellStates.FloorGoalCrate)); boundry = boundry.BitwiseOR(Map.ToBitmap(CellStates.FloorGoalPlayer)); boundry = boundry.BitwiseOR(Map.ToBitmap(CellStates.FloorPlayer)); return boundry; }
/// <summary> /// Render the current map /// </summary> /// <param name="Map"></param> /// <returns></returns> public Image Draw(SokobanMap Map) { if (Map == null) return null; Bitmap canvas = new Bitmap(Map.Size.X * tileSize.X, Map.Size.Y * tileSize.Y); Graphics gg = Graphics.FromImage(canvas); DrawWithGraphics(Map, gg); return canvas; }
/// <summary> /// Generate a static move map, ie. all positions for which a player could move to in the course of a puzzle /// </summary> /// <param name="Map"></param> /// <returns></returns> public static Bitmap GenerateStaticMoveMap(SokobanMap Map) { Bitmap boundry = GenerateBoundryMap(Map); // Flood fill from player position to remove any unreachable positions FloodFillStrategy floodFill = new FloodFillStrategy(boundry, Map.Player); Evaluator<LocationNode> eval = new Evaluator<LocationNode>(); eval.Evaluate(floodFill); return floodFill.Result; }
/// <summary> /// Find the shortest path from the player position to a destination position /// </summary> /// <param name="Map">Map</param> /// <param name="Destination">Destination goal position</param> /// <returns>NULL if no path is found</returns> public static List<VectorInt> FindPlayerPath(SokobanMap Map, VectorInt Destination) { Bitmap boundry = MapAnalysis.GenerateBoundryMap(Map); boundry = boundry.BitwiseOR(MapAnalysis.GenerateCrateMap(Map)); // Find all positble moves for the player FloodFillStrategy floodFill = new FloodFillStrategy(boundry, Map.Player); Evaluator<LocationNode> eval = new Evaluator<LocationNode>(); eval.Evaluate(floodFill); List<LocationNode> result = floodFill.GetShortestPath(Destination); if (result == null) return null; // Path found, convert to VectoInt return result.ConvertAll<VectorInt>(delegate(LocationNode item) { return item.Location; }); }
/// <summary> /// Strong Construction /// </summary> /// <param name="aMap"></param> public Game(Puzzle aPuzzle, SokobanMap aMap) { if (aMap == null) throw new ArgumentNullException("aPuzzle"); StringCollection sc = null; if (!aMap.isValid(out sc)) throw new Exception(sc[0]); puzzle = aPuzzle; startPuzzle = aMap; current = new SokobanMap(StartPuzzle); moves = new Stack<Move>(); stats = new Stats(); stats.Start = DateTime.MinValue; stats.End = DateTime.MinValue; bookmarks = new List<Bookmark>(); }
protected SolverResult Solve(SokobanMap puzzle) { using (CodeTimer timer = new CodeTimer("TestSolver.Solve(...)")) { SolverController controller = new SolverController(puzzle); try { System.Console.WriteLine(puzzle.ToString()); SolverResult results = controller.Solve(); if (results.Exception != null) { // Bubble up throw new Exception("Solver Failed Iternally", results.Exception); } if (results.Status == SolverResult.CalculationResult.SolutionFound) { // Check that Assert.IsTrue(results.HasSolution, "State says a solution was found, but none are listed in solutions list"); } if (results.HasSolution) { int cc = 0; foreach (Solution solution in results.Solutions) { string testRes = "Error"; Assert.IsTrue(solution.Test(puzzle, out testRes), testRes); Console.WriteLine("Testing solution: {0} - {1}", cc, testRes); cc++; } } return results; } finally { timer.Stop(); Console.WriteLine(controller.DebugReport.ToString(new DebugReportFormatter())); System.Console.WriteLine("Total Time: " + timer.Duration(1)); System.Console.WriteLine("---"); } } }
public void TestReverseStrategyCoreSimple() { CodeTimer timer = new CodeTimer(""); timer.Start(); try { SokobanMap map = new SokobanMap(); map.SetFromStrings(new string[] { "~##~#####", "##.##.O.#", "#.##.XO.#", "~##.X...#", "##.XP.###", "#.X..##~~", "#OO.##.##", "#...#~##~", "#####~#~~" }); PuzzleMap pMap = new PuzzleMap((Puzzle)null); pMap.Map = map; SolverController controller = new SolverController(pMap); controller.Init(); controller.State = SolverController.States.Running; // Manually set state, as we are not using the controller; but the strategy uses the controller to check if it should exit ReverseStrategy rev = new ReverseStrategy(controller); Evaluator<SolverNode> eval = new Evaluator<SolverNode>(); EvalStatus result = eval.Evaluate(rev); Assert.AreEqual(EvalStatus.CompleteSolution, result, "Should find a solution"); } finally { timer.Stop(); System.Console.WriteLine("Total Time: " + timer.Duration(1)); } }
/// <summary> /// Is the player adjacent to a puzzle position /// </summary> /// <param name="PuzzlePos"></param> /// <returns></returns> private static Direction IsAdjacentPlayer(SokobanMap Map, VectorInt PuzzlePos) { VectorInt playerPos = Map.Player; if (playerPos.Offset(Direction.Up) == PuzzlePos) return Direction.Up; if (playerPos.Offset(Direction.Down) == PuzzlePos) return Direction.Down; if (playerPos.Offset(Direction.Left) == PuzzlePos) return Direction.Left; if (playerPos.Offset(Direction.Right) == PuzzlePos) return Direction.Right; return Direction.None; }
/// <summary> /// Construct from a previous move /// </summary> /// <param name="aBefore"></param> /// <param name="aMoveDirection"></param> public Move(SokobanMap aBefore, Direction aMoveDirection, bool aIsPush) { Before = aBefore; MoveDirection = aMoveDirection; isPush = aIsPush; }
private SokobanMap BuildCurrentMap(SolverNode node) { SokobanMap result = new SokobanMap(); result.Init(node.CrateMap.Size); for (int cx = 0; cx < result.Size.Width; cx++) for (int cy = 0; cy < result.Size.Height; cy++) { if (controller.StaticAnalysis.WallMap[cx, cy]) result[cx, cy] = CellStates.Wall; if (controller.StaticAnalysis.FloorMap[cx, cy]) result[cx, cy] = CellStates.Floor; if (controller.StaticAnalysis.GoalMap[cx, cy]) result.SetState(new VectorInt(cx, cy), Cell.Goal); if (node.CrateMap[cx, cy]) result.SetState(new VectorInt(cx, cy), Cell.Crate); result.SetState(node.PlayerPosition, Cell.Player); } return result; }
public void TestFindCratePath() { SokobanMap map = new SokobanMap(); map.setFromStrings(new string[] { "~~~###~~~~~", "~~##.#~####", "~##..###..#", "##.X......#", "#...PX.#..#", "###.X###..#", "~~#..#OO..#", "~##.##O#.##", "~#......##~", "~#.....##~~", "~#######~~~" }); CrateAnalysis.ShortestCratePath result = CrateAnalysis.FindCratePath(map, new VectorInt(3, 3), new VectorInt(9, 5)); Assert.IsNotNull(result); Debug.WriteLine(map.ToString()); Debug.WriteLine(result.CratePath.ToString()); Debug.WriteLine(result.PlayerPath.ToString()); Debug.WriteLine("done."); }
protected SolverResult SolveFromString(string[] puzzle) { SokobanMap map = new SokobanMap(); map.SetFromStrings(puzzle); return Solve(map); }
public void TestFindCratePath_DeeperTest() { string mapString = @"~~~~~~~~~~~##### ~~~~~~~~~~##...# ~~~~~~~~~~#....# ~~~~####~~#.X.## ~~~~#..####X.X#~ ~~~~#.....X.X.#~ ~~~##.##.X.X.X#~ ~~~#..O#..X.X.#~ ~~~#..O#......#~ #####.#########~ #OOOO.P..#~~~~~~ #OOOO....#~~~~~~ ##..######~~~~~~ ~####~~~~~~~~~~~"; SokobanMap map = new SokobanMap(); map.setFromString(mapString); CrateAnalysis.ShortestCratePath result = CrateAnalysis.FindCratePath(map, new VectorInt(10, 7), new VectorInt(7, 5)); Assert.IsNull(result, "There is not cratemovepath to from position 10,7 to 7,5"); }
/// <summary> /// Perform a player move. This is the function that encapsulated the game moves. /// </summary> /// <param name="moveDir">Direction of move/push</param> /// <returns>Result of the move</returns> /// <remarks> /// Basic puzzle rules: /// <list type=""> /// <item>Player can push, never pull, a crate into a space</item> /// <item>There are no diagonal moves, or pushes</item> /// <item>When all crates are on goal positions the puzzle is complete</item> /// <item>Two crates cannot be pushed in a line</item> /// </list> /// </remarks> public virtual MoveResult Move(Direction moveDir) { // First Move? if (Stats.Start == DateTime.MinValue) { Stats.Start = DateTime.Now; } Stats.Moves++; Stats.MovesTotal++; VectorInt curr = Current.Player; VectorInt currstep = Current.Player.Offset(moveDir); VectorInt currstepstep = currstep.Offset(moveDir); CellStates p_curr = Current[curr]; CellStates p_currstep = Current[currstep]; CellStates p_currstepstep = CellStates.Void; if (Current.Rectangle.Contains(currstepstep)) p_currstepstep = Current[currstepstep]; // Is the move valid if (p_currstep == CellStates.Wall || p_currstep == CellStates.Void) return MoveResult.Invalid; // Next map SokobanMap next = new SokobanMap(Current); // Step? if (p_currstep == CellStates.Floor || p_currstep == CellStates.FloorGoal) { // Step, no push // New player pos if (p_currstep == CellStates.Floor) next[currstep] = CellStates.FloorPlayer; else next[currstep] = CellStates.FloorGoalPlayer; // Old pos if (p_curr == CellStates.FloorGoalPlayer) next[curr] = CellStates.FloorGoal; if (p_curr == CellStates.FloorPlayer) next[curr] = CellStates.Floor; Moves.Push(new Move(Current, moveDir, false)); Current = next; return MoveResult.ValidMove; } // Push? if (p_currstep == CellStates.FloorCrate || p_currstep == CellStates.FloorGoalCrate) { Stats.Pushes++; Stats.PushesTotal++; // Push a crate // Valid push? if (p_currstepstep == CellStates.Floor || p_currstepstep == CellStates.FloorGoal) { // Valid. // Old pos if (p_curr == CellStates.FloorGoalPlayer) next[curr] = CellStates.FloorGoal; if (p_curr == CellStates.FloorPlayer) next[curr] = CellStates.Floor; // Player Pos if (p_currstep == CellStates.FloorCrate) next[currstep] = CellStates.FloorPlayer; if (p_currstep == CellStates.FloorGoalCrate) next[currstep] = CellStates.FloorGoalPlayer; // Crate Pos if (p_currstepstep == CellStates.Floor) next[currstepstep] = CellStates.FloorCrate; if (p_currstepstep == CellStates.FloorGoal) next[currstepstep] = CellStates.FloorGoalCrate; Moves.Push(new Move(Current, moveDir, true)); Current = next; // Has the map been solved if (Current.isPuzzleComplete()) { Stats.End = DateTime.Now; return MoveResult.ValidPushWin; } // Normal push, or crate onto goal if (next[currstepstep] == CellStates.FloorGoalCrate) { return MoveResult.ValidPushGoal; } return MoveResult.ValidPush; } else { // Cannot push return MoveResult.Invalid; } } return MoveResult.Invalid; }
/// <summary> /// Convert a pixel location to a map cell location /// </summary> /// <returns></returns> public RectangleInt GetPixelPosition(SokobanMap Map, VectorInt cell) { VectorInt pos = cell.Divide(tileSize); return new RectangleInt(pos, pos.Add(Map.Size)); }
/// <summary> /// Reset to the start position /// </summary> public virtual void Reset() { Stats.Restarts++; Stats.Moves = 0; Stats.Pushes = 0; Current = new SokobanMap(StartPuzzle); Moves = new Stack<Move>(); }
/// <summary> /// Draw a puzzle /// </summary> /// <param name="Map"></param> /// <param name="DrawSource"></param> public void DrawWithGraphicsColours(SokobanMap Map, Graphics DrawSource) { for (int cx = 0; cx < Map.Size.X; cx++) for (int cy = 0; cy < Map.Size.Y; cy++) { DrawSource.FillRectangle(tileColours[(int)Map[new VectorInt(cx, cy)]], cx * tileSize.X, cy * tileSize.Y, tileSize.X, tileSize.Y); } }
/// <summary> /// Convert a pixel location to a map cell location /// </summary> /// <param name="Map"></param> /// <param name="pixel"></param> /// <returns></returns> public VectorInt GetCellPosition(SokobanMap Map, VectorInt pixel) { VectorInt pos = pixel.Divide(tileSize); if (pos.X >= Map.Size.X || pos.Y >= Map.Size.Y) return VectorInt.MinValue; return pos; }
/// <summary> /// Draw a puzzle /// </summary> /// <param name="Map"></param> /// <param name="DrawSource"></param> public void DrawWithGraphics(SokobanMap Map, Graphics DrawSource) { for (int cx = 0; cx < Map.Size.X; cx++) for (int cy = 0; cy < Map.Size.Y; cy++) { Image tile = tiles[(int) Map[new VectorInt(cx, cy)]]; if (tile != null) { DrawSource.DrawImage(tile,cx * tileSize.X, cy * tileSize.Y, tileSize.X, tileSize.Y); } } }
/// <summary> /// Build the result from the completed solver /// </summary> /// <param name="controller"></param> public void Build(SolverController controller) { map = controller.Map; // Build type strong info class info = new SolverResultInfo(); info.TotalNodes = (int) controller.Stats.Nodes.ValueTotal; info.TotalSeconds = controller.Stats.EvaluationTime.ValueTotal; info.Machine = DebugHelper.GetCPUDescription(); info.RatingScore = PuzzleAnalysis.CalcRating(map); foreach (Statistic stat in controller.Stats.Stats) { SolverLabel lb = stat.GetDisplayData(); info.InfoValues.Add(new NameValuePair() {Name = lb.Name, Value = lb.Value}); } // Build the exit status and summary if (HasSolution) { // General Sucess status = CalculationResult.SolutionFound; StringBuilder sb = new StringBuilder(); if (solutions.Count == 1) { sb.Append("Solution"); } else { sb.AppendFormat("{0} solutions", solutions.Count); } sb.AppendFormat(" in {0} and {1} nodes at {2:0} nodes/s (puzzle score {3})", StringHelper.ToString(TimeSpan.FromSeconds(info.TotalSeconds)), info.TotalNodes, info.TotalNodes/info.TotalSeconds, info.RatingScore); summary = sb.ToString(); } else { if (exception != null) { status = CalculationResult.Error; summary = string.Format("Error ({0})", exception.Message); } else { if (controllerResult == SolverController.States.Cancelled) { status = CalculationResult.Cancelled; summary = string.Format(" Cancelled after {0} sec and {1} nodes at {2:0} nodes/s (puzzle score {3})", StringHelper.ToString(TimeSpan.FromSeconds(info.TotalSeconds)), info.TotalNodes, info.TotalNodes/info.TotalSeconds, info.RatingScore); } else if (controllerResult == SolverController.States.CompleteNoSolution) { status = CalculationResult.NoSolution; summary = "The puzzle does not have a solution"; } else { status = CalculationResult.GaveUp; summary = string.Format( "The solver could not find a within the given constraints. Tried for {0} sec and {1} nodes at {2:0} nodes/s (puzzle score {3})", StringHelper.ToString(TimeSpan.FromSeconds(info.TotalSeconds)), info.TotalNodes, info.TotalNodes/info.TotalSeconds, info.RatingScore); } } } }
public static Bitmap GenerateWallMap(SokobanMap map) { Bitmap result = map.ToBitmap(CellStates.Wall); return result; }
/// <summary> /// Get a simple boundry map /// </summary> /// <param name="Map"></param> /// <returns></returns> public static Bitmap GenerateBoundryMap(SokobanMap Map) { Bitmap boundry = Map.ToBitmap(CellStates.Wall); boundry = boundry.BitwiseOR(Map.ToBitmap(CellStates.Void)); return boundry; }
/// <summary> /// Get map for the crates /// </summary> /// <param name="Map"></param> /// <returns></returns> public static Bitmap GenerateCrateMap(SokobanMap Map) { Bitmap result = Map.ToBitmap(CellStates.FloorCrate); result = result.BitwiseOR(Map.ToBitmap(CellStates.FloorGoalCrate)); return result; }