public void RunGen(StreamWriter str)
        {
            Func <int, string> outputFile = (gen) => Path.Combine(_outputFolder, $"output_{gen}.txt");
            Func <int, string> inputFile  = (gen) => Path.Combine(_outputFolder, $"input_{gen}.txt");
            Func <int, string> linesFile  = (gen) => Path.Combine(_outputFolder, $"lines_{gen}.txt");
            Func <int, string> tmpFile    = (gen) => Path.Combine(_outputFolder, $"tmp_{gen}.txt");
            Func <int, string> tmpFileC   = (gen) => Path.Combine(_outputFolder, $"tmpC_{gen}.txt");
            var lastGen = Enumerable.Range(0, _generations).Select(x => (int?)x).LastOrDefault(x => File.Exists(inputFile(x.GetValueOrDefault())));
            var parents = lastGen != null?File.ReadAllLines(inputFile(lastGen.Value)).Select(x => new Ai(x)).ToList()
                              : Enumerable.Range(0, _population).Select((x, i) => GeneratePlayer(i, 0)).ToList();

            File.WriteAllLines(inputFile(lastGen.GetValueOrDefault()), parents.Select(x => x.ToString()));



            var tmpParents = new List <Ai>();

            if (File.Exists(tmpFile(lastGen.GetValueOrDefault())))
            {
                tmpParents = File.ReadAllLines(tmpFile(lastGen.GetValueOrDefault())).Select(x => new Ai(x)).ToList();
            }
            var tmpChildren = new List <Ai>();

            if (File.Exists(tmpFileC(lastGen.GetValueOrDefault())))
            {
                tmpChildren = File.ReadAllLines(tmpFileC(lastGen.GetValueOrDefault())).Select(x => new Ai(x)).ToList();
            }

            _nextId = Math.Max(parents.Max(x => x.Id) + 1, tmpChildren.Max(x => x.Id) + 1);

            for (int generation = lastGen.GetValueOrDefault(); generation < _generations; generation++)
            {
                Func <Ai, int>    clearedLinesSelector = ai => ai.PreviousGames.Select(x => x.Item2).Sum();
                Func <Ai, int>    playedBlocksSelector = ai => ai.PreviousGames.Select(x => x.Item1).Sum();
                var               generation1          = generation;
                Func <Ai, double> selector             = x => clearedLinesSelector(x) + (generation1 - x.Generation) / (double)_generations;

                if (!File.Exists(linesFile(generation)))
                {
                    GenerateInput.Generate(_inputsCount, (_inputSize /* * (generation + 1)*/), linesFile(generation));
                }
                var games  = File.ReadAllLines(linesFile(generation));
                var inputs = games.Select(x => Enumerable.Range(0, x.Length)
                                          .Select(i => x.Substring(i, i + _aheadRead >= x.Length ? x.Length - i : _aheadRead))
                                          .ToList()).ToList();


                int counted = 0;
                if (!File.Exists(tmpFile(generation)))
                {
                    File.Create(tmpFile(generation)).Dispose();
                }
                foreach (var parent in parents)
                {
                    if (counted >= tmpParents.Count)
                    {
                        parent.PreviousGames = new ConcurrentBag <Tuple <int, int, int> >();
                        foreach (var input in inputs)
                        //Parallel.ForEach(inputs, input =>
                        {
                            var grid = parent.StartNewGame();
                            parent.PlayMoreMoves(input, ref grid);
                        }
                        //);
                        File.AppendAllLines(tmpFile(generation), new[] { parent.ToString() });
                        tmpParents.Add(parent);
                    }
                    var pl = tmpParents[counted];
                    Console.WriteLine($"{generation,5}_{pl.Id,5}: {playedBlocksSelector(pl),6}/{clearedLinesSelector(pl),6} [{pl.Generation,4}]    ({++counted})");
                }
                parents = tmpParents.OrderByDescending(selector).ToList();

                LogString(str, "Parents:");
                LogString(str, $"CL: {generation,5}: Avg: {parents.Select(x => clearedLinesSelector(x)).Average(),6}, Min: {parents.Select(x => clearedLinesSelector(x)).Min(),6}, Max: {parents.Select(x => clearedLinesSelector(x)).Max(),6}");
                LogString(str, $"PB: {generation,5}: Avg: {parents.Select(x => playedBlocksSelector(x)).Average(),6}, Min: {parents.Select(x => playedBlocksSelector(x)).Min(),6}, Max: {parents.Select(x => playedBlocksSelector(x)).Max(),6}");
                LogString(str, "\tRatio: " + parents.Select(x => playedBlocksSelector(x)).Max() / (double)parents.Select(x => clearedLinesSelector(x)).Max());
                //var ordered = players.OrderByDescending(selector).ToList();
                LogString(str,
                          $"\t{parents[0].Id}: {playedBlocksSelector(parents[0])}/{clearedLinesSelector(parents[0])} [{parents[0].Generation}] - {string.Join(" ", parents[0].Factors.Select(x => $"{x:0.00000000}"))}"); //{ordered[0].GapsW:0.00000000} {ordered[0].JumpsW:0.00000000} {ordered[0].HeightsW:0.00000000} {ordered[0].FullRowsW:0.00000000}");
                LogString(str,
                          $"\t{parents[1].Id}: {playedBlocksSelector(parents[1])}/{clearedLinesSelector(parents[1])} [{parents[1].Generation}] - {string.Join(" ", parents[1].Factors.Select(x => $"{x:0.00000000}"))}"); //{ordered[1].GapsW:0.00000000} {ordered[1].JumpsW:0.00000000} {ordered[1].HeightsW:0.00000000} {ordered[1].FullRowsW:0.00000000}");
                LogString(str, "");

                File.WriteAllText(outputFile(generation),
                                  string.Join(Environment.NewLine,
                                              parents.Select(x => x.ToString())));



                List <Ai> children = new List <Ai>();
                for (int i = 0; i < parents.Count; i++)
                {
                    for (int j = i + 1; j < parents.Count; j++)
                    {
                        children.Add(GenerateOffspring2(parents[i], parents[j], _nextId++, generation + 1, selector));
                    }
                }

                counted = 0;
                if (!File.Exists(tmpFileC(generation)))
                {
                    File.Create(tmpFileC(generation)).Dispose();
                }
                foreach (var child in children)
                {
                    if (counted >= tmpChildren.Count)
                    {
                        child.PreviousGames = new ConcurrentBag <Tuple <int, int, int> >();
                        foreach (var input in inputs)
                        //Parallel.ForEach(inputs, input =>
                        {
                            var grid = child.StartNewGame();
                            child.PlayMoreMoves(input, ref grid);
                        }
                        //);
                        File.AppendAllLines(tmpFileC(generation), new[] { child.ToString() });
                        tmpChildren.Add(child);
                    }
                    var pl = tmpChildren[counted];
                    Console.WriteLine($"{generation,5}_{pl.Id,5}: {playedBlocksSelector(pl),6}/{clearedLinesSelector(pl),6} [{pl.Generation,4}]    ({++counted})");
                }
                children = tmpChildren.OrderByDescending(selector).ToList();

                LogString(str, "");
                LogString(str, $"CL: {generation,5}: Avg: {children.Select(x => clearedLinesSelector(x)).Average(),6}, Min: {children.Select(x => clearedLinesSelector(x)).Min(),6}, Max: {children.Select(x => clearedLinesSelector(x)).Max(),6}");
                LogString(str, $"PB: {generation,5}: Avg: {children.Select(x => playedBlocksSelector(x)).Average(),6}, Min: {children.Select(x => playedBlocksSelector(x)).Min(),6}, Max: {children.Select(x => playedBlocksSelector(x)).Max(),6}");
                LogString(str, "\tRatio: " + children.Select(x => playedBlocksSelector(x)).Max() / (double)children.Select(x => clearedLinesSelector(x)).Max());
                //var ordered = players.OrderByDescending(selector).ToList();
                LogString(str,
                          $"\t{children[0].Id}: {playedBlocksSelector(children[0])}/{clearedLinesSelector(children[0])} [{children[0].Generation}] - {string.Join(" ", children[0].Factors.Select(x => $"{x:0.00000000}"))}"); //{ordered[0].GapsW:0.00000000} {ordered[0].JumpsW:0.00000000} {ordered[0].HeightsW:0.00000000} {ordered[0].FullRowsW:0.00000000}");
                LogString(str,
                          $"\t{children[1].Id}: {playedBlocksSelector(children[1])}/{clearedLinesSelector(children[1])} [{children[1].Generation}] - {string.Join(" ", children[1].Factors.Select(x => $"{x:0.00000000}"))}"); //{ordered[1].GapsW:0.00000000} {ordered[1].JumpsW:0.00000000} {ordered[1].HeightsW:0.00000000} {ordered[1].FullRowsW:0.00000000}");
                LogString(str, "");

                File.AppendAllLines(outputFile(generation), new List <string> {
                    ""
                });
                File.AppendAllLines(outputFile(generation), children.Select(x => x.ToString()));


                parents = parents.Concat(children).OrderByDescending(selector).Take(_population).ToList();



                //players = PrepareNextGeneration(players, generation + 1, selector);
                tmpParents.Clear();
                tmpChildren.Clear();
                File.WriteAllLines(inputFile(generation + 1), parents.Select(x => x.ToString()));
            }
        }
        public void RunGeneration(StreamWriter str)
        {
            Func <Ai, int> playedBlocksSelector  = ai => ai.PreviousGames.Select(x => x.Item1).Sum(); //ai.PreviousGames.Skip(ai.PreviousGames.Count - _inputsCount).Select(x => x.Item1).Sum();
            Func <Ai, int> clearedLinesSelector  = ai => ai.PreviousGames.Select(x => x.Item2).Sum(); //ai.PreviousGames.Skip(ai.PreviousGames.Count - _inputsCount).Select(x => x.Item2).Sum();
            Func <Ai, int> maxHeightSelector     = ai => ai.PreviousGames.Select(x => x.Item3).Max(); //ai.PreviousGames.Skip(ai.PreviousGames.Count - _inputsCount).Select(x => x.Item2).Sum();
            Func <Ai, int> maxHeightsSumSelector = ai => ai.PreviousGames.Select(x => x.Item3).Sum(); //ai.PreviousGames.Skip(ai.PreviousGames.Count - _inputsCount).Select(x => x.Item2).Sum();

            //var players = Enumerable.Range(0, _population).Select((x, i) => GeneratePlayer(i, 0)).ToList();
            var lastGen       = Enumerable.Range(0, _generations).Select(x => (int?)x).LastOrDefault(x => File.Exists(Path.Combine(_outputFolder, $"input_{x}.txt")));
            var inputFileName = Path.Combine(_outputFolder, $"input_{lastGen ?? 0}.txt");
            var players       = lastGen != null?File.ReadAllLines(inputFileName).Select(x => new Ai(x)).ToList()
                                    : Enumerable.Range(0, _population).Select((x, i) => GeneratePlayer(i, 0)).ToList();

            File.WriteAllLines(inputFileName, players.Select(x => x.ToString()));

            List <Ai> tmpPlayers  = new List <Ai>();
            string    tmpFileName = Path.Combine(_outputFolder, $"tmp_{lastGen}.txt");

            if (File.Exists(tmpFileName))
            {
                tmpPlayers = File.ReadAllLines(tmpFileName).Select(x => new Ai(x)).ToList();
            }

            for (int generation = lastGen ?? 0; generation < _generations; generation++)
            {
                var generation1            = generation;
                Func <Ai, double> selector = x => /*playedBlocksSelector(x) * 2 +*/ clearedLinesSelector(x) + (generation1 - x.Generation) / (double)_generations;
//                Func<Ai, double> selector =
//                    x =>
//                        -maxHeightSelector(x) - (maxHeightsSumSelector(x)/(_w*_h*_inputsCount)) -
//                        2.5/(playedBlocksSelector(x)/clearedLinesSelector(x));


                string fileName = Path.Combine(_outputFolder, $"inputsGen_{generation}.txt");

                if (!File.Exists(fileName))
                {
                    GenerateInput.Generate(_inputsCount, (_inputSize /* * (generation + 1)*/), fileName);
                }
                var games  = File.ReadAllLines(fileName);
                var inputs =
                    games.Select(
                        x =>
                        Enumerable.Range(0, x.Length)
                        .Select(i => x.Substring(i, i + _aheadRead >= x.Length ? x.Length - i : _aheadRead))
                        .ToList()).ToList();

                int counted = 0;
                tmpFileName = Path.Combine(_outputFolder, $"tmp_{generation}.txt");
                if (!File.Exists(tmpFileName))
                {
                    File.Create(tmpFileName).Dispose();
                }
                foreach (var player in players)
                //Parallel.ForEach(players, player =>
                {
                    if (counted >= tmpPlayers.Count)
                    {
                        player.PreviousGames = new ConcurrentBag <Tuple <int, int, int> >();
                        foreach (var input in inputs)
                        //Parallel.ForEach(inputs, input =>
                        {
                            var grid = player.StartNewGame();
                            player.PlayMoreMoves(input, ref grid);
                        }
                        //);
                        //player.EndRound(_inputsCount);
                        File.AppendAllLines(tmpFileName, new[] { player.ToString() });
                        tmpPlayers.Add(player);
                    }
                    var pl = tmpPlayers[counted];
                    //File.AppendAllLines("progress.tmp",new []{player.ToString()});
                    Console.WriteLine(
                        $"{generation,5}_{pl.Id,5}: {playedBlocksSelector(pl),6}/{clearedLinesSelector(pl),6}/{maxHeightSelector(pl),6} [{pl.Generation,4}]    ({++counted})");
                }
                players = tmpPlayers;

                //);


                LogString(str, "");
                LogString(str, $"CL: {generation,5}: Avg: {players.Select(x => clearedLinesSelector(x)).Average(),6}, Min: {players.Select(x => clearedLinesSelector(x)).Min(),6}, Max: {players.Select(x => clearedLinesSelector(x)).Max(),6}");
                LogString(str, $"PB: {generation,5}: Avg: {players.Select(x => playedBlocksSelector(x)).Average(),6}, Min: {players.Select(x => playedBlocksSelector(x)).Min(),6}, Max: {players.Select(x => playedBlocksSelector(x)).Max(),6}");
                LogString(str, $"MH: {generation,5}: Avg: {players.Select(x => maxHeightSelector(x)).Average(),6}, Min: {players.Select(x => maxHeightSelector(x)).Min(),6}, Max: {players.Select(x => maxHeightSelector(x)).Max(),6}");
                LogString(str, "\tRatio: " + players.Select(x => playedBlocksSelector(x)).Max() / (double)players.Select(x => clearedLinesSelector(x)).Max());
                var ordered = players.OrderByDescending(selector).ToList();
                LogString(str,
                          $"\t{ordered[0].Id}: {playedBlocksSelector(ordered[0])}/{clearedLinesSelector(ordered[0])}/{maxHeightSelector(ordered[0])} [{ordered[0].Generation}] - {string.Join(" ", ordered[0].Factors.Select(x => $"{x:0.00000000}"))}"); //{ordered[0].GapsW:0.00000000} {ordered[0].JumpsW:0.00000000} {ordered[0].HeightsW:0.00000000} {ordered[0].FullRowsW:0.00000000}");
                LogString(str,
                          $"\t{ordered[1].Id}: {playedBlocksSelector(ordered[1])}/{clearedLinesSelector(ordered[1])}/{maxHeightSelector(ordered[1])} [{ordered[1].Generation}] - {string.Join(" ", ordered[1].Factors.Select(x => $"{x:0.00000000}"))}"); //{ordered[1].GapsW:0.00000000} {ordered[1].JumpsW:0.00000000} {ordered[1].HeightsW:0.00000000} {ordered[1].FullRowsW:0.00000000}");
                LogString(str, "");

                File.WriteAllText(GetGenerationFilePath(generation),
                                  string.Join(Environment.NewLine,
                                              ordered.Select(x => x.ToString())));

                players = PrepareNextGeneration(players, generation + 1, selector);
                tmpPlayers.Clear();
                File.WriteAllLines(Path.Combine(_outputFolder, $"input_{generation+1}.txt"), players.Select(x => x.ToString()));
            }
        }