/// <summary>
    /// Rekursive Suchmethode einer einzelnen Kiste
    /// </summary>
    /// <param name="raum">Raum mit der einzelnen Kiste und Spielerstellung</param>
    /// <returns>Mindestzahl der Schritte zum Ziel (oder 60000 = wenn kein Ziel möglich)</returns>
    int RechneMindestZüge(SokowahnRaum raum)
    {
      List<List<SokowahnStellung>> list = new List<List<SokowahnStellung>>();
      list.Add(new List<SokowahnStellung>(raum.GetStellung().SelfEnumerable()));
      int listTiefe = 0;

      SokowahnHash_Index0 hash = new SokowahnHash_Index0();

      while (listTiefe < list.Count)
      {
        foreach (var stellung in list[listTiefe])
        {
          raum.LadeStellung(stellung);
          if (raumZiele[stellung.kistenZuRaum[0]]) return listTiefe;

          foreach (var variante in raum.GetVarianten())
          {
            int find = hash.Get(variante.crc64);
            if (find < 65535)
            {
              if (find <= variante.zugTiefe) continue;
              hash.Update(variante.crc64, variante.zugTiefe);
            }
            else
            {
              hash.Add(variante.crc64, variante.zugTiefe);
            }
            while (list.Count <= variante.zugTiefe)
            {
              list.Add(new List<SokowahnStellung>());
            }
            list[variante.zugTiefe].Add(variante);
          }
        }
        listTiefe++;
      }

      return 60000;
    }
    /// <summary>
    /// initialisiert die ersten Blocker-Kisten für den SammlerStart
    /// </summary>
    /// <param name="zielVariante">gibt an, ob nur Varianten ermittelt werden sollen, wo alle Kisten bereits auf dem Zielfeld stehen</param>
    void SammleKistenInit(bool zielVariante)
    {
      sammlerCheckKisten = Enumerable.Range(0, suchKistenAnzahl).Select(i => i).ToArray();
      sammlerCheckKisten[suchKistenAnzahl - 1]--; // letzte eins zurück setzen, damit beim ersten sammlerNext() auch die erste Variante gesetzt werden kann

      char[] feldData = basisRaum.FeldData;
      int feldBreite = basisRaum.FeldBreite;

      bool[] spielerRaum = SokowahnStaticTools.SpielfeldRaumScan(feldData, feldBreite);
      raumAnzahl = spielerRaum.Count(x => x);
      int[] raumZuFeld = Enumerable.Range(0, spielerRaum.Length).Where(i => spielerRaum[i]).ToArray();
      int[] feldZuRaum = Enumerable.Range(0, feldData.Length).Select(i => spielerRaum[i] ? raumZuFeld.ToList().IndexOf(i) : -1).ToArray();

      if (zielVariante)
      {
        sammlerCheckKistenRaum = raumZuFeld.Select(i => (feldData[i] == '.' || feldData[i] == '*' || feldData[i] == '+') ? feldZuRaum[i] : -1).Where(i => i >= 0).ToArray();
      }
      else
      {
        sammlerCheckKistenRaum = raumZuFeld.Select(i => (feldData[i] == '$' || feldData[i] == '*') ? feldZuRaum[i] : -1).Where(i => i >= 0).ToArray();
      }

      if (!zielVariante) // nur beim ersten mal Initialisieren
      {
        tmpRaum = new SokowahnRaum(feldData, feldBreite);
        tmpRaum.KistenAnzahl = suchKistenAnzahl;
        threadRäume = Enumerable.Range(0, 256).Select(i => new SokowahnRaum(tmpRaum)).ToArray();

        bekannteStellungen = new SokowahnHash_Index24Multi();

        prüfListe = new SokowahnLinearList2(suchKistenAnzahl + 1, Environment.CurrentDirectory + "\\temp\\", listeMax / 32768);
        prüfListeSammler = new SokowahnLinearList2(suchKistenAnzahl + 1, Environment.CurrentDirectory + "\\temp\\", listeMax / 32768);
        prüfListeGut = new SokowahnLinearList2(suchKistenAnzahl + 1, Environment.CurrentDirectory + "\\temp\\", listeMax / 32768);
        prüfListeBöse = new SokowahnLinearList2(suchKistenAnzahl + 1, Environment.CurrentDirectory + "\\temp\\", listeMax / 32768);
      }
    }
    /// <summary>
    /// Konstruktor
    /// </summary>
    /// <param name="spielFeld">Spielfeld als Textzeilen</param>
    public SokoWahn_5th(string spielFeld)
    {
      SokowahnStaticTools.SpielfeldEinlesen(spielFeld, out feldBreite, out feldHöhe, out feldSpielerStartPos, out feldData, out feldDataLeer);

      raumBasis = new SokowahnRaum(feldData, feldBreite);

      Directory.CreateDirectory(TempOrdner);

      // --- Vorwärtssuche initialisieren ---
      bekannteStellungen = new SokowahnHash_Index24Multi();
      bekannteStellungen.Add(raumBasis.Crc, 0);
      vorwärtsTiefe = 0;
      vorwärtsTiefeAktuell = 0;
      vorwärtsSucher = new SokowahnLinearList2[0];
      vorwärtsSucherPunkte = new Dictionary<int, int>[0];
      VorwärtsAdd(raumBasis.GetStellung(), new SokowahnPunkte());

      // --- Zielstellungen und Rückwärtssuche initialisieren ---
      zielStellungen = new SokowahnHash_Index24Multi();
      rückwärtsTiefe = 0;
      rückwärtsSucher = new SokowahnLinearList2[0];

      foreach (SokowahnStellung stellung in SokowahnStaticTools.SucheZielStellungen(raumBasis))
      {
        zielStellungen.Add(stellung.crc64, 60000);
        RückwärtsAdd(stellung);
      }

      ulong spielFeldCrc = Crc64.Start.Crc64Update(raumBasis.FeldBreite, raumBasis.FeldHöhe, raumBasis.FeldData);

      blocker = new SokowahnBlockerB2(Environment.CurrentDirectory + "\\temp\\blocker2_x" + spielFeldCrc.ToString("x").PadLeft(16, '0') + ".gz", raumBasis);
    }
    /// <summary>
    /// fügt eine Stellung in die Liste ein
    /// </summary>
    /// <param name="raum">Raum mit den entsprechenden Daten</param>
    public void Add(SokowahnRaum raum)
    {
      if (leseModus) throw new Exception("Fehler beim hinzufügen, Liste befindet siche bereits im Lese-Modus!");

      if (bufferPos == bufferGro)
      {
        if (bufferGro < bufferMax)
        {
          bufferGro = Math.Min(bufferGro * 2, bufferMax);
          Array.Resize(ref buffer, bufferGro * satzGröße);
        }
        else
        {
          TempSpeichern();
        }
      }

      int off = bufferPos * satzGröße;
      buffer[off] = (byte)raum.raumSpielerPos;
      for (int i = 1; i < satzGröße; i++) buffer[off + i] = (byte)raum.kistenZuRaum[i - 1];

      bufferPos++;
    }
 /// <summary>
 /// Konstruktor
 /// </summary>
 /// <param name="blockerDatei">Pfad zur Datei, worin sich eventuell archivierte Blocker-Daten befinden</param>
 /// <param name="basisRaum">Raum mit dem originalen Spielfeld</param>
 public SokowahnBlocker(string blockerDatei, SokowahnRaum basisRaum)
 {
   this.basisRaum = basisRaum;
   bekannteBlocker = new BlockerFeld[0];
   status = BlockerStatus.Init;
   suchKistenAnzahl = 0;
   this.blockerDatei = blockerDatei;
   if (File.Exists(blockerDatei))
   {
     LadeAlleBlocker();
   }
 }
    /// <summary>
    /// gibt eine bestimmte Stellung direkt als sichtbaren String aus (ohne die eigene Stellung zu beeinflussen)
    /// </summary>
    /// <param name="data">Stellungsdaten, welche ausgelesen werden sollen</param>
    /// <param name="offset">Startposition im Array</param>
    /// <returns>lesbare Stellung</returns>
    public string Debug(ushort[] data, int offset, int zugTiefe)
    {
      SokowahnRaum tmp = new SokowahnRaum(this);

      tmp.LadeStellung(data, offset, zugTiefe);

      return tmp.ToString();
    }
 /// <summary>
 /// Konstruktor
 /// </summary>
 /// <param name="blockerDatei">Pfad zur Datei, worin sich eventuell archivierte Blocker-Daten befinden</param>
 /// <param name="basisRaum">Raum mit dem originalen Spielfeld</param>
 public SokowahnBlockerB2(string blockerDatei, SokowahnRaum basisRaum)
 {
   this.basisRaum = basisRaum;
   this.blockerDatei = blockerDatei;
   this.blockerStatus = BlockerStatus.init;
   //if (file.exists(blockerdatei))
   //{
   //  ladealleblocker();
   //}
 }
  /// <summary>
  /// ermittelt alle möglichen Zielstellungen
  /// </summary>
  /// <param name="raumBasis">Raumsystem mit Basis-Stellung</param>
  /// <returns>Enumerable aller möglichen Zielstellungen</returns>
  public static IEnumerable<SokowahnStellung> SucheZielStellungen(SokowahnRaum raumBasis)
  {
   int feldBreite = raumBasis.FeldBreite;
   int feldHöhe = raumBasis.FeldHöhe;
   char[] feldData = raumBasis.FeldDataLeer.Select(c => c == '.' ? '*' : c).ToArray();

   for (int spielerY = 1; spielerY < feldHöhe - 1; spielerY++)
   {
    for (int spielerX = 1; spielerX < feldBreite - 1; spielerX++)
    {
     int pos = spielerX + spielerY * feldBreite;
     if (feldData[pos] == ' ' && (feldData[pos - 1] == '*' || feldData[pos + 1] == '*' || feldData[pos - feldBreite] == '*' || feldData[pos + feldBreite] == '*'))
     {
      feldData[pos] = '@';
      SokowahnRaum tmpRaum = new SokowahnRaum(feldData, feldBreite);
      tmpRaum.SpielerZugTiefe = 60000;

      var findStellungen = tmpRaum.GetVariantenRückwärts().ToArray();

      if (findStellungen.Length > 0)
      {
       yield return tmpRaum.GetStellung();
//       foreach (var findStellung in findStellungen) yield return findStellung;
      }

      feldData[pos] = ' ';
     }
    }
   }

   yield break;
  }
    /// <summary>
    /// ermittelt alle Zielstellungen, welche mit der entsprechenden Anzahl der Kisten erreichbar sind
    /// </summary>
    /// <returns>Enumerable aller möglichen Zielstellungen</returns>
    IEnumerable<SokowahnStellung> SammlerBerechneZielStellungen()
    {
      SokowahnRaum raum = new SokowahnRaum(basisRaum);
      raum.KistenAnzahl = sammlerKistenAnzahl;

      char[] feldData = basisRaum.FeldData;
      int feldBreite = basisRaum.FeldBreite;

      bool[] spielerRaum = SokowahnStaticTools.SpielfeldRaumScan(feldData, feldBreite);
      int[] raumZuFeld = Enumerable.Range(0, spielerRaum.Length).Where(i => spielerRaum[i]).ToArray();
      int[] feldZuRaum = Enumerable.Range(0, feldData.Length).Select(i => spielerRaum[i] ? raumZuFeld.ToList().IndexOf(i) : -1).ToArray();

      var sammlerKistenRaum = raumZuFeld.Select(i => (feldData[i] == '.' || feldData[i] == '*' || feldData[i] == '+') ? feldZuRaum[i] : -1).Where(i => i >= 0).ToArray();

      foreach (var variante in Tools.BerechneElementeVarianten(basisRaum.KistenAnzahl, sammlerKistenAnzahl, false))
      {
        raum.LadeStellung(variante, sammlerKistenRaum);
        foreach (var stellung in raum.GetVariantenBlockerZiele())
        {
          yield return stellung;
        }
      }
      yield break;
    }
 /// <summary>
 /// gibt die eigene Stellung als lesbares Feld zurück
 /// </summary>
 /// <param name="tmpRaum">Sokowahn-Raum mit den jeweiligen Grunddaten</param>
 /// <returns>lesbares Spielfeld</returns>
 public string Debug(SokowahnRaum raum)
 {
   SokowahnRaum temp = new SokowahnRaum(raum);
   temp.KistenAnzahl = kistenZuRaum.Length;
   return temp.Debug(this);
 }
 /// <summary>
 /// gibt die eigene Stellung als lesbares Feld zurück
 /// </summary>
 /// <param name="tmpRaum">Sokowahn-Raum mit den jeweiligen Grunddaten</param>
 /// <returns>lesbares Spielfeld</returns>
 public string ToString(SokowahnRaum raum)
 {
   return Debug(raum);
 }
 /// <summary>
 /// gibt die eigene Stellung als lesbares Feld zurück
 /// </summary>
 /// <param name="tmpRaum">Sokowahn-Raum mit den jeweiligen Grunddaten</param>
 /// <returns>lesbares Spielfeld</returns>
 public string Debug(SokowahnRaum raum)
 {
   return raum.Debug(this);
 }
    /// <summary>
    /// Konstruktor
    /// </summary>
    /// <param name="raum">Basisdaten anhand eines vorhanden Raumes nutzen</param>
    /// <param name="blocker">bekannte Blocker</param>
    /// <param name="startHash">bekannte Einträge in der Start-Hashtable</param>
    /// <param name="zielHash">bekannte Einträge in der Ziel-Hashtable</param>
    public SokowahnRaum(SokowahnRaum raum, ISokowahnBlocker blocker, ISokowahnHash startHash, ISokowahnHash zielHash)
    {
      this.feldData = raum.feldData;
      this.feldBreite = raum.feldBreite;
      this.raumAnzahl = raum.raumAnzahl;
      this.raumLinks = raum.raumLinks;
      this.raumRechts = raum.raumRechts;
      this.raumOben = raum.raumOben;
      this.raumUnten = raum.raumUnten;

      this.raumSpielerPos = raum.raumSpielerPos;
      this.spielerZugTiefe = raum.spielerZugTiefe;
      this.raumZuKisten = raum.raumZuKisten.ToArray(); // Kopie erstellen
      this.kistenAnzahl = raum.kistenAnzahl;
      this.kistenZuRaum = raum.kistenZuRaum.ToArray(); // Kopie erstellen

      this.tmpCheckRaumPosis = new int[raumAnzahl];
      this.tmpCheckRaumTiefe = new int[raumAnzahl];
      this.tmpRaumCheckFertig = new bool[raumAnzahl + 1];
      this.tmpRaumCheckFertig[raumAnzahl] = true; // Ende-Feld schon auf fertig setzen

      this.merkBlocker = blocker;
      this.merkStartHash = startHash;
      this.merkZielHash = zielHash;
    }
    /// <summary>
    /// Konstruktor
    /// </summary>
    /// <param name="raum">Basisdaten anhand eines vorhanden Raumes nutzen</param>
    public SokowahnRaum(SokowahnRaum raum)
    {
      this.feldData = raum.feldData;
      this.feldBreite = raum.feldBreite;
      this.raumAnzahl = raum.raumAnzahl;
      this.raumLinks = raum.raumLinks;
      this.raumRechts = raum.raumRechts;
      this.raumOben = raum.raumOben;
      this.raumUnten = raum.raumUnten;

      this.raumSpielerPos = raum.raumSpielerPos;
      this.spielerZugTiefe = raum.spielerZugTiefe;
      this.raumZuKisten = raum.raumZuKisten.ToArray(); // Kopie erstellen
      this.kistenAnzahl = raum.kistenAnzahl;
      this.kistenZuRaum = raum.kistenZuRaum.ToArray(); // Kopie erstellen

      this.tmpCheckRaumPosis = new int[raumAnzahl];
      this.tmpCheckRaumTiefe = new int[raumAnzahl];
      this.tmpRaumCheckFertig = new bool[raumAnzahl + 1];
      this.tmpRaumCheckFertig[raumAnzahl] = true; // Ende-Feld schon auf fertig setzen
    }
    /// <summary>
    /// berechnet die Anzahl der Schritte um eine einzelne Kiste zum Ziel zu bewegen
    /// </summary>
    /// <param name="raum">Raum mit der einzelnen Kiste</param>
    /// <returns>Mindestzahl der Schritte zum Ziel (oder 60000 = wenn kein Ziel möglich)</returns>
    int RechneMinZügeKiste(SokowahnRaum raum)
    {
      var varianten = raum.GetVariantenBlockerZiele().ToArray();

      int mindest = 60000;

      foreach (var variante in varianten)
      {
        raum.LadeStellung(variante);
        raum.spielerZugTiefe = 0;

        int i = RechneMindestZüge(raum);

        if (i < mindest) mindest = i;
      }

      return mindest;
    }
    /// <summary>
    /// berechnet die nächsten Blocker
    /// </summary>
    /// <param name="limit">maximale Anzahl der Berechnungen, oder 0, wenn die Berechnung beendet werden soll</param>
    /// <returns>true, wenn noch weitere Berechnungen anstehen</returns>
    public bool Next(int limit)
    {
      switch (blockerStatus)
      {
        #region # case BlockerStatus.init:
        case BlockerStatus.init:
        {
          sammlerKistenAnzahl++;
          if (sammlerKistenAnzahl == basisRaum.KistenAnzahl)
          {
            Abbruch();
            return false;
          }

          sammlerAbfrage = SammlerBerechneZielStellungen().GetEnumerator();
          sammlerHash = new SokowahnHash_Index16Multi();
          sammlerStats = new long[60001];
          rückwärtsSucher = Enumerable.Range(0, maxVorschau).Select(x => new SokowahnLinearList2(sammlerKistenAnzahl + 1, Environment.CurrentDirectory + "\\temp\\", multi32k)).ToArray();
          rückwärtsTiefe = 0;

          alleStellungen = new SokowahnLinearList2(sammlerKistenAnzahl + 1, Environment.CurrentDirectory + "\\temp\\", multi32k * 4);
          alleBlockerHash = new HashSet<ulong>();

          char[] feldData = basisRaum.FeldData;
          int feldBreite = basisRaum.FeldBreite;

          bool[] spielerRaum = SokowahnStaticTools.SpielfeldRaumScan(feldData, feldBreite);
          raumAnzahl = spielerRaum.Count(x => x);

          tempBlocker = new SokowahnBlockerB.BlockerFeld[raumAnzahl];
          for (int i = 0; i < tempBlocker.Length; i++) tempBlocker[i].kistenNummerLeer = sammlerKistenAnzahl;

          blockerStatus = BlockerStatus.sammleZiele;
          return true;
        }
        #endregion

        #region # case BlockerStatus.sammleZiele:
        case BlockerStatus.sammleZiele:
        {
          while (limit-- > 0 && sammlerAbfrage.MoveNext())
          {
            var satz = sammlerAbfrage.Current;
            if (sammlerHash.Get(satz.crc64) == 65535)
            {
              sammlerHash.Add(satz.crc64, 60000);
              sammlerStats[60000]++;
              rückwärtsSucher[0].Add(satz.raumSpielerPos, satz.kistenZuRaum);
              alleStellungen.Add(satz.raumSpielerPos, satz.kistenZuRaum);
            }
          }

          if (limit >= 0)
          {
            tempRaum = new SokowahnRaum(basisRaum);
            tempRaum.KistenAnzahl = sammlerKistenAnzahl;
            threadRäume = Enumerable.Range(0, 256).Select(i => new SokowahnRaum(tempRaum)).ToArray();
            blockerStatus = BlockerStatus.suchModus;
          }

          return true;
        }
        #endregion

        #region # case BlockerStatus.suchModus:
        case BlockerStatus.suchModus:
        {
          if (SucheRückwärts(limit)) return true;

          Array.Resize(ref bekannteBlockerHashes, sammlerKistenAnzahl);
          bekannteBlockerHashes[sammlerKistenAnzahl - 1] = sammlerHash;
          Array.Resize(ref bekannteSammlerStats, sammlerKistenAnzahl);
          var tmp = sammlerStats.Reverse().ToList();
          while (tmp[tmp.Count - 1] == 0) tmp.RemoveAt(tmp.Count - 1);
          bekannteSammlerStats[sammlerKistenAnzahl - 1] = tmp.ToArray();
          for (int i = 0; i < rückwärtsSucher.Length; i++) rückwärtsSucher[i].Dispose();
          blockerStatus = BlockerStatus.blockerSuche;

          return true;
        }
        #endregion

        #region # case BlockerStatus.blockerSuche:
        case BlockerStatus.blockerSuche:
        {
          limit = (int)Math.Min((long)limit, alleStellungen.SatzAnzahl);

          if (limit == 0)
          {
            int startPos = bekannteBlocker.Length;
            Array.Resize(ref bekannteBlocker, startPos + raumAnzahl);
            for (int i = 0; i < raumAnzahl; i++) bekannteBlocker[startPos + i] = tempBlocker[i];

            for (int i = 0; i < bekannteBlocker.Length; i++)
            {
              bekannteBlocker[i].geprüfteStellungen = sammlerHash.HashAnzahl;
              bekannteBlocker[i].kistenNummerLeer = sammlerKistenAnzahl + 1;
            }

            long geprüfteStellungenGesamt = 0;
            for (int i = 0; i < bekannteBlocker.Length; i += raumAnzahl) geprüfteStellungenGesamt += bekannteBlocker[i].geprüfteStellungen;
            for (int i = 0; i < bekannteBlocker.Length; i++) bekannteBlocker[i].Sortieren();

            blockerStatus = BlockerStatus.init;
            return true;
          }

          var stellungen = alleStellungen.Pop(limit);
          int satzGröße = alleStellungen.SatzGröße;

          var ergebnisse = Enumerable.Range(0, limit)
#if !parallelDeaktivieren
.AsParallel()
#if parallelGeordnet
.AsOrdered()
#endif
#endif
.SelectMany(stellung =>
          {
            SokowahnRaum raum = threadRäume[Thread.CurrentThread.ManagedThreadId];
            raum.LadeStellung(stellungen, stellung * satzGröße, 0);
            //            return raum.GetVarianten(this).Where(x => sammlerHash.Get(x.crc64) == 65535);
            var ausgabe = raum.GetVarianten(this).Where(x =>
              {
                if (sammlerHash.Get(x.crc64) == 65535) return true;
                x.zugTiefe = 60000 - sammlerHash.Get(x.crc64);
                //string dbg = x.Debug(basisRaum);
                return false;
              }
              );
            return ausgabe;
          }).ToArray();

          foreach (var stellung in ergebnisse)
          {
            if (alleBlockerHash.Contains(stellung.crc64)) continue;
            alleBlockerHash.Add(stellung.crc64);
            tempBlocker[stellung.raumSpielerPos].Add(stellung.kistenZuRaum, sammlerKistenAnzahl);
          }

          return true;
        }
        #endregion

        #region # case BlockerStatus.bereit:
        case BlockerStatus.bereit:
        {
          return false;
        }
        #endregion

        default: throw new NotImplementedException();
      }
    }
    /// <summary>
    /// Normale Suche nach vorne (von der Startstellung aus beginnend)
    /// </summary>
    /// <param name="limit">maximale Anzahl der Rechenschritte</param>
    /// <returns>true, wenn noch weitere Berechnungen anstehen</returns>
    bool SucheVorwärts(int limit)
    {
      if (limit == 0)
      {
        vorwärtsTiefeAktuell = vorwärtsTiefe;
        return true;
      }

      if (vorwärtsTiefe >= vorwärtsSucher.Length) return false;

      if (limit > 1111100000)
      {
        maxZüge = limit - 1111100000 + 1;
        for (int i = maxZüge; i < vorwärtsSucher.Length; i++)
        {
          vorwärtsSucher[i].Dispose();
          vorwärtsSucher[i] = null;
          vorwärtsSucherPunkte[i].Clear();
          vorwärtsSucherPunkte[i] = null;
        }
        Array.Resize(ref vorwärtsSucher, Math.Min(vorwärtsSucher.Length, maxZüge));
        Array.Resize(ref vorwärtsSucherPunkte, vorwärtsSucher.Length);
        MessageBox.Show("Maxzüge gesetzt auf: " + (maxZüge - 1));
        vorwärtsTiefeAktuell = vorwärtsTiefe;
        return true;
      }

      bool schnell = limit >= 100000;

      var liste = vorwärtsSucher[vorwärtsTiefeAktuell];
      var listePunkte = vorwärtsSucherPunkte[vorwärtsTiefeAktuell];

      //if (liste.SatzAnzahl * 2L > (long)limit)
      //{
      //  if (liste.SatzAnzahl < (long)limit * 10L)
      //  {
      //    limit = (int)(liste.SatzAnzahl / 2L);
      //  }
      //}

      limit = (int)Math.Min((long)limit, liste.SatzAnzahl);

      #region # // --- raumPool abfragen bzw. erstellen ---
      if (raumPool == null)
      {
        raumPool = Enumerable.Range(0, 256).Select(x => new SokowahnRaum(raumBasis, blocker, bekannteStellungen, zielStellungen)).ToArray();

        SokowahnRaum r = new SokowahnRaum(raumPool[0]);

        r.KistenAnzahl = 1;

        int[] kistePos = new int[1];
        int[] kisteIndex = new int[1];
        int raumAnzahl = r.RaumAnzahl;
        einzelKistenDauer = new int[raumAnzahl];

        char[] feldLeer = raumPool[0].FeldDataLeer;
        bool[] spielerRaum = SokowahnStaticTools.SpielfeldRaumScan(feldData, feldBreite);
        int[] raumZuFeld = Enumerable.Range(0, spielerRaum.Length).Where(i => spielerRaum[i]).ToArray();
        raumZiele = new bool[raumAnzahl];
        for (int i = 0; i < raumAnzahl; i++) raumZiele[i] = feldLeer[raumZuFeld[i]] == '.';

        for (int k = 0; k < raumAnzahl; k++)
        {
          kistePos[0] = k;
          r.LadeStellung(kisteIndex, kistePos);
          einzelKistenDauer[k] = RechneMinZügeKiste(r);
        }

        laufFelder = new ushort[raumAnzahl * raumAnzahl];

        ushort[] laufPosis = new ushort[raumAnzahl + 1];
        for (int y = 0; y < laufFelder.Length; y += raumAnzahl)
        {
          int pp = 0;
          laufPosis[pp++] = (ushort)(y / raumAnzahl);
          for (int x = 0; x < raumAnzahl; x++)
          {
            if (x > pp) throw new Exception("Fatal!");
            ushort p = laufPosis[x];
            laufFelder[y + x] = p;
            if ((laufPosis[pp] = (ushort)r.raumOben[p]) < raumAnzahl && !laufPosis.Take(pp).Any(i => i == laufPosis[pp])) pp++;
            if ((laufPosis[pp] = (ushort)r.raumRechts[p]) < raumAnzahl && !laufPosis.Take(pp).Any(i => i == laufPosis[pp])) pp++;
            if ((laufPosis[pp] = (ushort)r.raumUnten[p]) < raumAnzahl && !laufPosis.Take(pp).Any(i => i == laufPosis[pp])) pp++;
            if ((laufPosis[pp] = (ushort)r.raumLinks[p]) < raumAnzahl && !laufPosis.Take(pp).Any(i => i == laufPosis[pp])) pp++;
          }
        }

      }

      #endregion

      #region # // --- Teilliste mit den besten Stellungen erzeugen (falls notwendig) ---
      if (liste.SatzAnzahl > limit)
      {
        int punkteOk = 0;
        int findAnzahl = 0;
        int gutDazu = 0;
        foreach (var satz in listePunkte.OrderBy(x => x.Key))
        {
          punkteOk = satz.Key;
          findAnzahl += satz.Value;
          if (findAnzahl >= limit)
          {
            gutDazu = limit - (findAnzahl - satz.Value);
            break;
          }
        }

        var listeOk = new SokowahnLinearList2(raumBasis.KistenAnzahl + 1 + 2, TempOrdner);
        var listeAufheben = new SokowahnLinearList2(raumBasis.KistenAnzahl + 1 + 2, TempOrdner);

        long bis = liste.SatzAnzahl;
        for (long i = 0; i < bis; i++)
        {
          var stellung = liste.Pop();
          SokowahnPunkte punkte = new SokowahnPunkte(stellung);
          if (punkte.tiefeMax <= punkteOk)
          {
            if (punkte.tiefeMax == punkteOk)
            {
              if (gutDazu > 0)
              {
                gutDazu--;
              }
              else
              {
                listeAufheben.Add(stellung);
                continue;
              }
            }
            listeOk.Add(stellung);
            if (listeOk.SatzAnzahl == limit) break;
          }
          else
          {
            listeAufheben.Add(stellung);
          }
        }

        if (liste.SatzAnzahl < listeAufheben.SatzAnzahl)
        {
          bis = liste.SatzAnzahl;
          for (long i = 0; i < bis; i++) listeAufheben.Add(liste.Pop());
          vorwärtsSucher[vorwärtsTiefeAktuell] = listeAufheben;
          liste.Dispose();
        }
        else
        {
          bis = listeAufheben.SatzAnzahl;
          for (long i = 0; i < bis; i++) liste.Add(listeAufheben.Pop());
          listeAufheben.Dispose();
        }

        liste = listeOk;
      }

#if DEBUG
      if (limit != liste.SatzAnzahl) throw new Exception("aua?");
#endif
      #endregion

      SokowahnRaum raum = raumPool[Thread.CurrentThread.ManagedThreadId];

      int mx = maxZüge - rückwärtsTiefe;
      var ergebnisse = Enumerable.Range(0, limit).Select(i => liste.Pop()).SelectMany(stellung =>
      {
        raum.LadeStellung(stellung, vorwärtsTiefeAktuell);
        SokowahnPunkte punkte = new SokowahnPunkte(stellung);

        listePunkte[punkte.tiefeMax]--;
        if (bekannteStellungen.Get(raum.Crc) < vorwärtsTiefeAktuell) return Enumerable.Empty<SokowahnStellungRun>();

        return raum.GetVariantenRun().Where(v => v.zugTiefe <= mx && v.zugTiefe < bekannteStellungen.Get(v.crc64));
      }).ToArray();

#if !parallelDeaktivieren
      var punkteListe = new SokowahnPunkte[ergebnisse.Length];

      if (schnell)
      {
        ParallelEnumerable.Range(0, ergebnisse.Length).Select(v =>
        {
          SokowahnRaum r = raumPool[Thread.CurrentThread.ManagedThreadId];
          r.LadeStellung(ergebnisse[v]);
          punkteListe[v] = r.BerechnePunkte2(einzelKistenDauer, laufFelder);
          //punkteListe[v] = r.BerechnePunkteSchnell(einzelKistenDauer);
          return true;
        }).Count();
      }
      else
      {
        ParallelEnumerable.Range(0, ergebnisse.Length).Select(v =>
        {
          SokowahnRaum r = raumPool[Thread.CurrentThread.ManagedThreadId];
          r.LadeStellung(ergebnisse[v]);
          punkteListe[v] = r.BerechnePunkte(einzelKistenDauer);
          return true;
        }).Count();
      }
#endif

      for (int v = 0; v < ergebnisse.Length; v++)
      {
        var variante = ergebnisse[v];
        int findQuelle = bekannteStellungen.Get(variante.crc64);

#if parallelDeaktivieren
        raum.LadeStellung(variante);
        //        SokowahnPunkte punkte = raum.BerechnePunkte(einzelKistenDauer);
        SokowahnPunkte punkte = raum.BerechnePunkte2(einzelKistenDauer, laufFelder);
        //        SokowahnPunkte punkte = raum.BerechnePunkte3(einzelKistenDauer, laufFelder);
#else
        var punkte = punkteListe[v];
#endif

        if (variante.zugTiefe < findQuelle) // neue Stellung oder bessere Variante gefunden
        {
          int findZiel = zielStellungen.Get(variante.crc64);
          if (findZiel < 65535)
          {
            if (variante.zugTiefe + 60000 - findZiel < maxZüge)
            {
              #region // --- neue (bessere) Variante gefunden ---
              maxZüge = variante.zugTiefe + 60000 - findZiel;
              //gefundenCrc = variante.crc64;

              for (int i = maxZüge; i < vorwärtsSucher.Length; i++)
              {
                vorwärtsSucher[i].Dispose();
                vorwärtsSucher[i] = null;
                vorwärtsSucherPunkte[i].Clear();
                vorwärtsSucherPunkte[i] = null;
              }
              if (maxZüge < vorwärtsSucher.Length)
              {
                Array.Resize(ref vorwärtsSucher, maxZüge);
                Array.Resize(ref vorwärtsSucherPunkte, maxZüge);
              }
              #endregion
            }
            continue;
          }
          //if (punkte.tiefeMin + variante.zugTiefe < maxZüge)
          //if (variante.zugTiefe + rückwärtsTiefe < maxZüge)
          if (variante.zugTiefe + rückwärtsTiefe < maxZüge && punkte.tiefeMin + variante.zugTiefe < maxZüge)
          {
            if (findQuelle < 65535) bekannteStellungen.Update(variante.crc64, variante.zugTiefe); else bekannteStellungen.Add(variante.crc64, variante.zugTiefe);
            VorwärtsAdd(variante, punkte);
          }
        }
      }

      vorwärtsTiefeAktuell++;

      while (vorwärtsTiefe < vorwärtsSucher.Length && vorwärtsSucher[vorwärtsTiefe].SatzAnzahl == 0)
      {
        vorwärtsTiefe++;
        vorwärtsTiefeAktuell = vorwärtsTiefe;
      }

      if (vorwärtsTiefeAktuell == vorwärtsSucher.Length)
      {
        vorwärtsTiefeAktuell = vorwärtsTiefe;
      }

      return true;
    }
    /// <summary>
    /// gibt eine bestimmte Stellung direkt als sichtbaren String aus (ohne die eigene Stellung zu beeinflussen)
    /// </summary>
    /// <param name="data">Stellungsdaten, welche ausgelesen werden sollen</param>
    /// <param name="offset">Startposition im Array</param>
    /// <returns>lesbare Stellung</returns>
    public string Debug(byte[] data, int offset)
    {
      SokowahnRaum tmp = new SokowahnRaum(this);

      tmp.LadeStellung(data, offset, 0);

      return tmp.ToString();
    }