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;
        }
		public string Solve(Problem problem, int seed, string[] powerPhrases)
		{
			var finalPowerPhraseBuilder = new SimplePowerPhraseBuilder(powerPhrases);
			var spelledPhrases = new bool[powerPhrases.Length];
			var solution = new List<MoveType>();
			var game = new SolverGame(problem, seed, powerPhrases);
			while (true)
			{
				switch (game.state)
				{
					case GameBase.State.WaitUnit:
						game.Step();
						break;
					case GameBase.State.UnitInGame:
						var reachablePositions = new ReachablePositionsWithWords(game.map, powerPhrases, spelledPhrases);
						var evaluatePositions = new EvaluatePositions2(game.map);
						var endPositions = reachablePositions.EndPositions(game.currentUnit);
						var estimated = new Dictionary<Unit, double>();
						var bestPosition = endPositions.ArgMax(p =>
						{
							double value;
							if (estimated.TryGetValue(p.Item1, out value))
								return value;
							return estimated[p.Item1] = evaluatePositions.Evaluate(p.Item1);
						});
						var score = evaluatePositions.Evaluate(bestPosition.Item1);
						var wayToBestPosition = bestPosition.Item2;
						var unitSolution = staticPowerPhraseBuilder.Build(wayToBestPosition.path);
						SolutionAdded(game, unitSolution);
						game.ApplyUnitSolution(unitSolution);
						spelledPhrases = wayToBestPosition.spelledWords;
						solution.AddRange(wayToBestPosition.path);
						break;
					case GameBase.State.EndInvalidCommand:
					case GameBase.State.EndPositionRepeated:
						throw new InvalidOperationException(string.Format("Invalid state: {0}", game.state));
					case GameBase.State.End:
						return finalPowerPhraseBuilder.Build(solution);
					default:
						throw new ArgumentOutOfRangeException();
				}
			}
		}
        private IEnumerable<State> FindNextStates(State state)
        {
            var map = state.lastmap.Clone();
            map.LockUnit(state.lastUnit);
            map.RemoveLines();

            var nextUnit = units[state.nunits];
            var reachablePositions = new ReachablePositionsWithWords(map, powerPhrases, state.spelledPhrases);
            var evaluatePositions = new EvaluatePositions2(map);
            var endPositions = reachablePositions.EndPositions(nextUnit);
            var estimated = new Dictionary<Unit, Tuple<double, int>>();
            Tuple<Unit, ReachablePositionsWithWords.VisitedInfo>[] bestPositions = endPositions.OrderByDescending(
                p => {
                    Tuple<double, int> value;
                         var unit = p.Item1;
                         if (!estimated.TryGetValue(unit, out value))
                             value = estimated[p.Item1] = evaluatePositions._Evaluate(p.Item1);
                         return value.Item1;
                     }).Take(100).ToArray();

            foreach (var position in bestPositions)
            {
                var unit = position.Item1;
                var ndropped = estimated[unit].Item2;
                var move_score = CountPoints(unit.members.Count, state.lastDroopedLines, estimated[unit].Item2);
                var score = state.score + move_score;
                yield return new State()
                {
                    lastmap = map,
                    lastUnit = unit,
                    nunits = state.nunits + 1,
                    lastDroopedLines = ndropped,
                    score = score,
                    solution = state.solution.Concat(position.Item2.path).ToArray(),
                    spelledPhrases = position.Item2.spelledWords,
                    sumPositionEstimates = state.sumPositionEstimates + 10 + estimated[unit].Item1
                };
            }
        }
        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;
        }