예제 #1
0
    /// <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();
    }
예제 #2
0
    /// <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;
    }
예제 #3
0
    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;
          }
        }
      }

    }
예제 #4
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();
    }
예제 #5
0
    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;
    }
예제 #6
0
    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;
    }
예제 #7
0
    /// <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);
        }
      }
    }
예제 #8
0
    /// <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
    }