private static Tuple<double, Tuple<Unit, ReachablePositionsWithWords.VisitedInfo>[]> FindBestPositions_Recursive(int unitsAhead, Map map, Unit[] units, int unitIndex, string[] powerPhrases, bool[] spelledPhrases)
        {
            if (unitsAhead < 0 || unitIndex >= units.Length)
            {
                return Tuple.Create(0.0, new Tuple<Unit, ReachablePositionsWithWords.VisitedInfo>[0]);
            }
            var reachablePositions = new ReachablePositionsWithWords(map, powerPhrases, spelledPhrases);
            //var reachablePositions = new ReachablePositions(map);
            var evaluatePositions = new EvaluatePositions2(map);
            var unit = units[unitIndex];
            var endPositions = reachablePositions.SingleEndPositions(unit);
            var estimated = new Dictionary<Unit, double>();
            double? bestScore = null;
            var topOrderedPositions = endPositions
                .Select(p =>
                        {
                            double value;
                            if (estimated.TryGetValue(p.Item1, out value))
                                return Tuple.Create(value, p);
                            var score = estimated[p.Item1] = evaluatePositions.Evaluate(p.Item1);
                            return Tuple.Create(score, p);
                        })
                //.OrderByDescending(t => t.Item1)
                .OrderByDescending(t => (int)Math.Round(t.Item1 * 100))
                .ThenByDescending(t => t.Item2.Item2.score)
                .TakeWhile(tt =>
                           {
                               if (!bestScore.HasValue)
                               {
                                   bestScore = tt.Item1;
                                   return true;
                               }
                               return Math.Abs(tt.Item1 - bestScore.Value) < 1e-6;
                           }).ToList();

            if (!topOrderedPositions.Any())
            {
                return Tuple.Create(-1e3, new Tuple<Unit, ReachablePositionsWithWords.VisitedInfo>[0]);
            }

            var bestPosistions = Tuple.Create(double.MinValue, new Tuple<Unit, ReachablePositionsWithWords.VisitedInfo>[0]);
            foreach (var position in topOrderedPositions)
            {
                var newMap = map.Clone();
                newMap.LockUnit(position.Item2.Item1);
                newMap.RemoveLines();

                var nextPositions = FindBestPositions_Recursive(unitsAhead - 1, newMap, units, unitIndex + 1, powerPhrases, position.Item2.Item2.spelledWords);

                var score = position.Item1 + nextPositions.Item1;
                if (bestPosistions.Item1 < score)
                {
                    bestPosistions = Tuple.Create(score, new[] {position.Item2}.Concat(nextPositions.Item2).ToArray());
                }
            }

            return bestPosistions;
        }
        private Tuple<double, Tuple<Unit, ReachablePositionsWithWords.VisitedInfo>[]> FindBestPositions_Recursive(int unitsAhead, Map map, Unit[] units, int unitIndex, string[] powerPhrases, bool[] spelledPhrases)
        {
            if (unitsAhead < 0 || unitIndex >= units.Length)
            {
                return Tuple.Create(0.0, new Tuple<Unit, ReachablePositionsWithWords.VisitedInfo>[0]);
            }
            var reachablePositions = new ReachablePositionsWithWords(map, powerPhrases, spelledPhrases);
            //var reachablePositions = new ReachablePositions(map);
            //			var evaluatePositions = new EvaluatePositions(map);
            var evaluatePositions = new EvaluatePositions2(map);
            var unit = units[unitIndex];
            var endPositions = reachablePositions.SingleEndPositions(unit);
            var estimated = new Dictionary<Unit, double>();
            var topOrderedPositions = endPositions
                .Select(p =>
                        {
                            double value;
                            if (estimated.TryGetValue(p.Item1, out value))
                                return Tuple.Create(value, p);
                            var score = estimated[p.Item1] = evaluatePositions.Evaluate(p.Item1);
                            return Tuple.Create(score, p);
                        })
                //.OrderByDescending(t => t.Item1)
                .OrderByDescending(t => (int)Math.Round(t.Item1 * 100) + t.Item2.Item2.score)
                .ToList();

            var equallyTop = GetEquallyGoodTop(topOrderedPositions);
            var positionsForLookingAhead = topOrderedPositions.Take(Math.Max(equallyTop, minTopUnitCount)).ToList();

            if (!positionsForLookingAhead.Any())
            {
                return Tuple.Create(-1e3, new Tuple<Unit, ReachablePositionsWithWords.VisitedInfo>[0]);
            }

            var bestPosistions = Tuple.Create(double.MinValue, new Tuple<Unit, ReachablePositionsWithWords.VisitedInfo>[0]);
            foreach (var position in positionsForLookingAhead)
            {
                var newMap = map.Clone();
                newMap.LockUnit(position.Item2.Item1);
                newMap.RemoveLines();

                var nextPositions = FindBestPositions_Recursive(unitsAhead - 1, newMap, units, unitIndex + 1, powerPhrases, position.Item2.Item2.spelledWords);

                var score = position.Item1 + nextPositions.Item1;
                if (bestPosistions.Item1 < score)
                {
                    bestPosistions = Tuple.Create(score, new[] {position.Item2}.Concat(nextPositions.Item2).ToArray());
                }
            }

            return bestPosistions;
        }