/// <summary> /// einfaches Tool zum finden irgendeiner Lösung eines Spielfeldes /// </summary> /// <param name="field">Spielfeld, welches gescannt werden soll</param> static void MiniSolver(SokowahnField field) { var scanner = new SokowahnField(field); int stateLen = scanner.posis.Length; var todoBuf = new ushort[16777216 * stateLen]; int todoPos = 0; int todoLen = 0; foreach (var p in scanner.posis) todoBuf[todoLen++] = p; var nextBuf = new ushort[stateLen * (stateLen - 1) * 4]; var hashCrcs = new HashSet<ulong>(); while (todoPos < todoLen) { scanner.SetGameState(todoBuf, todoPos); todoPos += stateLen; if (todoLen - todoPos == 0 || (hashCrcs.Count & 0xfff) == 0) { Console.Clear(); Console.WriteLine(scanner); Console.WriteLine(); Console.WriteLine("Todo: " + ((todoLen - todoPos) / stateLen).ToString("N0") + " (" + (200.0 / todoBuf.Length * (todoLen - todoPos)).ToString("N1") + " %)"); Console.WriteLine("Hash: " + hashCrcs.Count.ToString("N0") + " (" + (100.0 / 48000000 * hashCrcs.Count).ToString("N1") + " %)"); } scanner.SetPlayerTopLeft(); var crc = scanner.GetGameStateCrc(); if (hashCrcs.Contains(crc)) continue; hashCrcs.Add(crc); int nextCount = scanner.ScanMoves(nextBuf); for (int i = 0; i < nextCount * stateLen; i++) todoBuf[todoLen++] = nextBuf[i]; if (todoPos * 2 > todoBuf.Length) { for (int i = todoPos; i < todoLen; i++) todoBuf[i - todoPos] = todoBuf[i]; todoLen -= todoPos; todoPos = 0; } } Console.ReadLine(); }
/// <summary> /// analysiert alle möglichen Kistenstellungen mit einfachen (langsamen) Methoden /// </summary> /// <param name="field">Feld, welches durchsucht werden soll</param> /// <param name="minBoxes">minimale Anzahl der Kisten, welche berechnet werden sollen</param> /// <param name="maxBoxes">maximale Anzahl der Kisten, welche berechnet werden sollen</param> static List<Dictionary<ulong, ushort>> MiniSolverHashBuilder(SokowahnField field, int minBoxes = 1, int maxBoxes = int.MaxValue) { var scanner = new SokowahnField(field); var targetFields = scanner.fieldData.Select((c, i) => new { c, i }).Where(f => f.c == '.' || f.c == '*').Select(f => (ushort)f.i).ToArray(); if (targetFields.Length < maxBoxes) maxBoxes = targetFields.Length; if (minBoxes < 1 || minBoxes > maxBoxes) throw new ArgumentException("minBoxes"); var hashResults = new List<Dictionary<ulong, ushort>>(); for (int boxesCount = minBoxes; boxesCount <= maxBoxes; boxesCount++) { // --- Variablen initialisieren --- var boxes = new ushort[boxesCount]; int stateLen = 1 + boxes.Length; var todoBuf = new ushort[16777216 * 2 / (stateLen + 1) * (stateLen + 1)]; int todoPos = 0, todoLen = 0; var stopWatch = Stopwatch.StartNew(); // --- Startaufgaben scannen und setzen --- { int emptyPlayerPos = scanner.fieldData.ToList().IndexOf(' '); int[] playerDirections = { -1, +1, -scanner.width, +scanner.width }; var checkDuplicates = new HashSet<ulong>(); foreach (var boxesVariant in SokoTools.FieldBoxesVariantsStatic(targetFields.Length, boxesCount).Select(v => v.SelectArray(f => targetFields[f]))) { scanner.SetPlayerPos(emptyPlayerPos); scanner.SetBoxes(boxesVariant); var fieldData = scanner.fieldData; foreach (ushort box in boxesVariant) { foreach (int playerDir in playerDirections) { int playerPos = box - playerDir; if (fieldData[playerPos] == '#' || fieldData[playerPos] == '$' || fieldData[playerPos] == '*') continue; int revPos = playerPos - playerDir; if (fieldData[revPos] == '#' || fieldData[revPos] == '$' || fieldData[revPos] == '*') continue; scanner.SetPlayerPos(playerPos); scanner.SetPlayerTopLeft(); ulong crc = scanner.GetGameStateCrc(); if (checkDuplicates.Contains(crc)) continue; checkDuplicates.Add(crc); todoBuf[todoLen++] = 0; todoLen += scanner.GetGameState(todoBuf, todoLen); } } } } Console.WriteLine(field.ToString()); Console.WriteLine(); // --- Aufgaben weiter rückwärts gerichtet abarbeiten --- { var hash = new Dictionary<ulong, ushort>(); var nextBuf = new ushort[stateLen * boxesCount * 4]; int limitTickCount = Environment.TickCount + 1000; while (todoPos < todoLen) { ushort depth = todoBuf[todoPos++]; scanner.SetGameState(todoBuf, todoPos); todoPos += stateLen; scanner.SetPlayerTopLeft(); ulong crc = scanner.GetGameStateCrc(); if (hash.ContainsKey(crc)) continue; hash.Add(crc, depth); if ((hash.Count & 0xff) == 0 && Environment.TickCount > limitTickCount) { limitTickCount = Environment.TickCount + 1000; Console.WriteLine("[" + boxesCount + "] (" + depth + ") " + ((todoLen - todoPos) / (stateLen + 1)).ToString("N0") + " / " + hash.Count.ToString("N0")); } depth++; int nextLength = scanner.ScanReverseMoves(nextBuf) * stateLen; for (int next = 0; next < nextLength; next += stateLen) { todoBuf[todoLen++] = depth; for (int i = 0; i < stateLen; i++) todoBuf[todoLen++] = nextBuf[next + i]; } if (todoBuf.Length - todoLen < nextLength * 2) { Array.Copy(todoBuf, todoPos, todoBuf, 0, todoLen - todoPos); todoLen -= todoPos; todoPos = 0; } } stopWatch.Stop(); Console.WriteLine(); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("[" + boxesCount + "] ok. Hash: " + hash.Count.ToString("N0") + " (" + stopWatch.ElapsedMilliseconds.ToString("N0") + " ms)"); Console.ForegroundColor = ConsoleColor.Gray; Console.WriteLine(); hashResults.Add(hash); } } return hashResults; }
static void ScanBlocker(SokowahnField field) { Console.WriteLine(field.ToString()); Console.WriteLine(); var test = new DeadlockBlocker(field); int width = field.width; var allPossibleBoxPositions = test.blockerSingle.Select((b, i) => new { b, i }).Where(x => !x.b).SelectArray(x => x.i); int boxDistanceLimit = SokoTools.MaxBoxDistance(allPossibleBoxPositions, 0, allPossibleBoxPositions.Length, width); var areas = new int[field.fieldData.Length]; int boxCount = 2; var nextBuf = new ushort[boxCount * 4 * (boxCount + 1)]; for (int boxDistance = 1; boxDistance <= boxDistanceLimit; boxDistance++) { Func<int[], int, bool> validateMethod = (boxes, index) => !test.blockerSingle[boxes[index]] && SokoTools.MaxBoxDistance(boxes, 0, index + 1, width) <= boxDistance; foreach (var set in SokoTools.FieldBoxesVariantsExtended(field.fieldData.Length, boxCount, validateMethod)) { if (test.blockerSingle[set[set.Length - 1]]) continue; if (SokoTools.MaxBoxDistance(set, 0, set.Length, width) != boxDistance) continue; test.ScanAreasWithBoxes(set, areas); field.SetGameState(areas[2], set.SelectArray(x => (ushort)x)); if (field.boxesRemain == 0) continue; int nextCount = field.ScanMoves(nextBuf); if (nextCount == 0) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("--- DEAD ---\r\n"); Console.ForegroundColor = ConsoleColor.Gray; } Console.WriteLine(field.ToString()); if (nextCount == 0) { int stop = 0; } } } }
static void ScanTopLeftFields(SokowahnField field) { boxesHash = new Dictionary<int, HashSet<ulong>>(); for (int b = 1; b <= field.boxesCount; b++) { Console.Clear(); Console.WriteLine(); for (int known = 1; known < b; known++) { Console.WriteLine(" known Boxes: " + known + " - Hash: " + boxesHash[known].Count.ToString("N0") + " Nodes"); } Console.WriteLine(); Console.WriteLine(" --- Calc Boxes: " + b + " ---"); Console.WriteLine(); int time = Environment.TickCount; var hash = MiniSolverHashBuilder(field, b, b); time = Environment.TickCount - time; boxesHash.Add(b, new HashSet<ulong>(hash.First().Keys)); if (time > 10000) break; if (time > 250 && b == field.boxesCount - 1) break; } Console.Clear(); var view = new SokowahnField(field); int maxBoxes = field.boxesCount; int width = field.width; var ways = FilterWays((ushort)field.PlayerPos, width, new HashSet<ushort>(Enumerable.Range(0, field.fieldData.Length).Select(f => (ushort)f)), new HashSet<ushort>(field.fieldData.Select((c, i) => new { c, i = (ushort)i }).Where(x => x.c == '#').Select(x => x.i))); #region # // --- ungültige Einzel-Box Positionen suchen --- invalidBoxes = new HashSet<ushort>(); foreach (ushort box in ways) { var checkCrc = new List<ulong>(); if (ways.Contains((ushort)(box - 1))) { view.SetGameState(new[] { (ushort)(box - 1), box }); view.SetPlayerTopLeft(); checkCrc.Add(view.GetGameStateCrc()); } if (ways.Contains((ushort)(box + 1))) { view.SetGameState(new[] { (ushort)(box + 1), box }); view.SetPlayerTopLeft(); checkCrc.Add(view.GetGameStateCrc()); } if (ways.Contains((ushort)(box - width))) { view.SetGameState(new[] { (ushort)(box - width), box }); view.SetPlayerTopLeft(); checkCrc.Add(view.GetGameStateCrc()); } if (ways.Contains((ushort)(box + width))) { view.SetGameState(new[] { (ushort)(box + width), box }); view.SetPlayerTopLeft(); checkCrc.Add(view.GetGameStateCrc()); } if (!checkCrc.Any(crc => boxesHash[1].Contains(crc))) { invalidBoxes.Add(box); } } #endregion var todo = new Queue<TopLeftTodo>(); var map = new List<int> { ways.Count }; foreach (var f in ways.OrderBy(x => x)) { map.Add(0); // Index Platzhalter todo.Enqueue(new TopLeftTodo { mapIndex = map.Count - 1, state = new[] { f }, known = new ushort[0] }); } int tick = 0; int nextTick = 0; while (todo.Count > 0) { bool dbg = false; if (map.Count > nextTick) { int t = Environment.TickCount; if (t > tick + 100) { Console.Title = "remain: " + todo.Count.ToString("N0") + " / " + map.Count.ToString("N0") + " (" + (Process.GetCurrentProcess().WorkingSet64 / 1048576.0).ToString("N1") + " MB)"; tick = t; dbg = true; } nextTick += 1000; } var next = todo.Dequeue(); var result = TestScan(width, next, ways, view, dbg); if (result != null) { if (result.Length <= 1) { map[next.mapIndex] = -result[0]; } else { map[next.mapIndex] = map.Count; map.Add(result.Length - 1); var known = new HashSet<ushort>(next.known); for (int r = 1; r < result.Length; r++) { map.Add(result[r]); known.Add(result[r]); map.Add(0); // Index Platzhalter if (next.state.Length <= maxBoxes) { var newState = AppendBoxesNewArray(next.state, result[r]); newState[0] = result[r - 1]; todo.Enqueue(new TopLeftTodo { mapIndex = map.Count - 1, state = newState, lastBox = result[r], known = known.ToArray() }); } } } } else { map[next.mapIndex] = -1; // ungültige Position if (dbg) { nextTick -= 1000; tick = 0; } } } Console.WriteLine(); Console.WriteLine("Map-Size: " + map.Count.ToString("N0") + " (" + (map.Count / 262144.0).ToString("N1") + " MB)"); Console.WriteLine(); }
static bool CheckDeadlock(TopLeftTodo topLeftTodo, SokowahnField view, ushort playerTopLeft) { HashSet<ulong> bHash; int boxesCount = topLeftTodo.state.Length - 1; if (boxesHash.TryGetValue(boxesCount, out bHash)) { ulong crc = Crc64.Start.Crc64Update(playerTopLeft).Crc64Update(topLeftTodo.state, 1, boxesCount); if (!bHash.Contains(crc)) { return true; } } else { // --- schnelle Vorprüfung --- for (int b = 1; b < topLeftTodo.state.Length; b++) { if (invalidBoxes.Contains(topLeftTodo.state[b])) return true; } // --- bestmögliche Hashprüfung --- int boxesCountMin = boxesHash.Count; bHash = boxesHash[boxesCountMin]; var checkBoxes = topLeftTodo.state.Skip(1).Where(b => b != topLeftTodo.lastBox).ToArray(); var checkState = new ushort[1 + boxesCountMin]; checkState[0] = playerTopLeft; foreach (var variant in SokoTools.FieldBoxesVariantsStatic(checkBoxes.Length, boxesCountMin - 1)) { for (int v = 0; v < variant.Length; v++) checkState[v + 1] = checkBoxes[variant[v]]; AppendBoxes(checkState, topLeftTodo.lastBox); view.SetGameState(checkState); view.SetPlayerTopLeft(); ulong crc = view.GetGameStateCrc(); if (!bHash.Contains(crc)) { return true; } } } return false; }
static ushort[] TestScan(int width, TopLeftTodo topLeftTodo, HashSet<ushort> ways, SokowahnField view, bool debug = true) { var state = topLeftTodo.state; view.SetGameState(state); var result = ScanBestTopLeftWay(state[0], width, ways, new HashSet<ushort>(state.Skip(1))); if (state.Length > 1 && CheckDeadlock(topLeftTodo, view, result[result.Count - 1])) return null; if (debug) { Console.SetCursorPosition(0, Math.Max(0, Console.CursorTop - 2)); Console.WriteLine(new string(' ', Console.WindowWidth - 1)); Console.SetCursorPosition(0, 1); Console.WriteLine(view.ToString()); Console.WriteLine(); string line = state[0] + " - " + string.Join(", ", result.Skip(1)); Console.WriteLine(line); Console.WriteLine(); } var known = new HashSet<ushort>(topLeftTodo.known); if (known.Contains(result[result.Count - 1])) return new[] { result[result.Count - 1] }; var resultFiltered = result.Where(f => !known.Contains(f)).ToArray(); return resultFiltered; }
/// <summary> /// startet ein Mini-Konsolen Spiel /// </summary> /// <param name="field">Spielfeld, was gespielt werden soll</param> public static void Run(SokowahnField field) { var game = new SokowahnField(field); var steps = new Stack<ushort[]>(); for (; ; ) { string output = game.ToString(); int playerChar = output.IndexOfAny(new[] { '@', '+' }); Console.Clear(); Console.ForegroundColor = ConsoleColor.Gray; Console.Write(output.Substring(0, playerChar)); Console.ForegroundColor = ConsoleColor.Yellow; Console.Write(output[playerChar]); Console.ForegroundColor = ConsoleColor.Gray; Console.Write(output.Remove(0, playerChar + 1)); Console.WriteLine(); Console.WriteLine(); Console.WriteLine("Steps: " + steps.Count.ToString("N0")); Console.WriteLine(); Console.WriteLine("Remain: " + game.boxesRemain); if (game.boxesRemain == 0) return; bool step = false; var oldState = game.GetGameState(); switch (Console.ReadKey(true).Key) { case ConsoleKey.Escape: return; case ConsoleKey.A: case ConsoleKey.NumPad4: case ConsoleKey.LeftArrow: step = game.MoveLeft(); break; case ConsoleKey.D: case ConsoleKey.NumPad6: case ConsoleKey.RightArrow: step = game.MoveRight(); break; case ConsoleKey.W: case ConsoleKey.NumPad8: case ConsoleKey.UpArrow: step = game.MoveUp(); break; case ConsoleKey.S: case ConsoleKey.NumPad2: case ConsoleKey.DownArrow: step = game.MoveDown(); break; case ConsoleKey.Z: case ConsoleKey.Delete: case ConsoleKey.Backspace: { if (steps.Count == 0) break; game.SetGameState(steps.Pop(), 0); } break; default: continue; } if (step) { steps.Push(oldState); } } }
/// <summary> /// scannt nach einzelnen Felder-Positionen, wo keine Kisten stehen dürfen /// </summary> void ScanBlockerSingle() { var targetFields = field.fieldData.Select((c, i) => new { c, i }).Where(f => wayMap[f.i] && (f.c == '.' || f.c == '*')).Select(f => (ushort)f.i).ToArray(); var boxFields = field.fieldData.Select((c, i) => new { c, i }).Where(f => wayMap[f.i] && (f.c == '$' || f.c == '*')).Select(f => (ushort)f.i).ToArray(); var scanner = new SokowahnField(field); var fieldData = scanner.fieldData; int emptyPlayerPos = scanner.fieldData.ToList().IndexOf(' '); int[] playerDirections = { -1, +1, -scanner.width, +scanner.width }; var todoStates = new Queue<ushort[]>(); const int StateLen = 2; #region # // --- Rückwärts-Suche vorbereiten --- foreach (ushort box in targetFields) { scanner.SetGameState(new[] { (ushort)emptyPlayerPos, box }); foreach (int playerDir in playerDirections) { int playerPos = box - playerDir; if (fieldData[playerPos] == '#' || fieldData[playerPos] == '$' || fieldData[playerPos] == '*') continue; int revPos = playerPos - playerDir; if (fieldData[revPos] == '#' || fieldData[revPos] == '$' || fieldData[revPos] == '*') continue; scanner.SetPlayerPos(playerPos); scanner.SetPlayerTopLeft(); todoStates.Enqueue(scanner.GetGameState()); } } #endregion #region # // --- Rückwärts-Suche durchführen --- var reverseHash = new HashSet<ulong>(); // alle Stellungen, welche mit einer Kiste rückwärts erreichbar sind var nextBuf = new ushort[StateLen * (1) * 4]; while (todoStates.Count > 0) { scanner.SetGameState(todoStates.Dequeue()); scanner.SetPlayerTopLeft(); ulong crc = scanner.GetGameStateCrc(); if (reverseHash.Contains(crc)) continue; reverseHash.Add(crc); int nextLength = scanner.ScanReverseMoves(nextBuf) * StateLen; for (int next = 0; next < nextLength; next += StateLen) { todoStates.Enqueue(new[] { nextBuf[next], nextBuf[next + 1] }); } } #endregion #region # // --- Vorwärts-Suche vorbereiten --- foreach (ushort box in boxFields) { todoStates.Enqueue(new[] { (ushort)field.PlayerPos, box }); } #endregion #region # // --- Vorwärts-Suche durchführen --- var forwardHash = new HashSet<ulong>(); // alle Stellungen, welche mit einer Kiste vorwärts erreichbar sind var forwardBoxPosis = new HashSet<ushort>(); // alle Positionen, wo eine Kiste stehen könnte while (todoStates.Count > 0) { var gameState = todoStates.Dequeue(); scanner.SetGameState(gameState); scanner.SetPlayerTopLeft(); ulong crc = scanner.GetGameStateCrc(); if (forwardHash.Contains(crc)) continue; forwardHash.Add(crc); if (!reverseHash.Contains(crc)) continue; forwardBoxPosis.Add(gameState[1]); int nextLength = scanner.ScanMoves(nextBuf) * StateLen; for (int next = 0; next < nextLength; next += StateLen) { todoStates.Enqueue(new[] { nextBuf[next], nextBuf[next + 1] }); } } #endregion #region # // --- geblockte Felder markieren, wo niemals eine Kiste stehen darf --- for (ushort i = 0; i < blockerSingle.Length; i++) { if (!blockerSingle[i] && !forwardBoxPosis.Contains(i)) { blockerSingle[i] = true; } } #endregion }