/// <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 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; }
/// <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 }