public RandomWalkAi(
     IStartPathStrategy startPathStrategy,
     IPathEstimator estimator,
     bool useAllowedDirections,
     bool useTerritoryTtl,
     bool killWithMinimax)
 {
     this.startPathStrategy    = startPathStrategy;
     this.estimator            = estimator;
     this.useAllowedDirections = useAllowedDirections;
     randomPath              = new RandomPathGenerator();
     reliablePathBuilder     = new ReliablePathBuilder(useTerritoryTtl);
     allowedDirectionsFinder = new AllowedDirectionsFinder(4, killWithMinimax);
 }
        public RequestOutput GetCommand(State state, int player, ITimeManager timeManager, Random random)
        {
            if (commands == null)
            {
                commands = new Direction[state.players.Length];
            }

            randomPath.random = random;

            distanceMap.Build(state);
            facts.Build(state, distanceMap);

            if (TryKillOpponent(state, player, out var kill))
            {
                return(kill);
            }

            backup.Backup(state);
            estimator.Before(state, player);

            var allowedDirectionsMask = useAllowedDirections
                ? allowedDirectionsFinder.GetAllowedDirectionsMask(timeManager.GetNested(70), state, player, distanceMap, facts)
                : (byte)0xFF;

            if (allowedDirectionsMask != 0 && (allowedDirectionsMask & (allowedDirectionsMask - 1)) == 0)
            {
                for (int d = 0; d < 4; d++)
                {
                    if ((allowedDirectionsMask & (1 << d)) != 0)
                    {
                        return new RequestOutput {
                                   Command = (Direction)d, Debug = $"Only one allowed direction. AllowedDirections: {AllowedDirectionsFinder.DescribeAllowedDirectionsMask(allowedDirectionsMask)} (depth {allowedDirectionsFinder.minimax.bestDepth})"
                        }
                    }
                    ;
                }
            }

            if (allowedDirectionsFinder.killWithMinimax)
            {
                var       bestKillScore   = 0.0;
                Direction?bestKillCommand = null;
                for (int d = 0; d < 4; d++)
                {
                    if ((allowedDirectionsMask & (1 << d)) != 0 && allowedDirectionsFinder.minimax.bestResultScores[d] > bestKillScore)
                    {
                        bestKillScore = allowedDirectionsFinder.minimax.bestResultScores[d];

                        bestKillCommand = (Direction)d;
                    }
                }

                if (bestKillScore > AllowedDirectionsFinder.killScore)
                {
                    return new RequestOutput {
                               Command = bestKillCommand, Debug = $"Gotcha with minimax! AllowedDirections: {AllowedDirectionsFinder.DescribeAllowedDirectionsMask(allowedDirectionsMask)} (depth {allowedDirectionsFinder.minimax.bestDepth})"
                    }
                }
                ;
            }

            var       pathCounter           = 0;
            var       validPathCounter      = 0;
            Direction?bestDir               = null;
            double    bestScore             = 0;
            var       bestLen               = 0;
            string    bestPath              = null;
            long      simulations           = 0;
            long      bestPathCounter       = 0;
            var       opponentCapturedFound = false;

            while (allowedDirectionsMask != 0 && !timeManager.IsExpired)
            {
                ++pathCounter;
                if (randomPath.Generate(state, player, distanceMap, facts, reliablePathBuilder, allowedDirectionsMask))
                {
                    ++validPathCounter;

                    var dir = default(Direction);
                    for (var i = 0; i < state.players.Length; i++)
                    {
                        if (i != player)
                        {
                            facts.pathsToOwned[i].Reset();
                        }
                        else
                        {
                            facts.pathsToOwned[i].BuildPath(state, reliablePathBuilder, i);
                            dir = facts.pathsToOwned[i].CurrentAction();
                        }
                    }

                    while (true)
                    {
                        for (var i = 0; i < state.players.Length; i++)
                        {
                            if (state.players[i].status == PlayerStatus.Eliminated || state.players[i].status == PlayerStatus.Broken)
                            {
                                continue;
                            }
                            if (state.players[i].arriveTime != 0)
                            {
                                continue;
                            }

                            commands[i] = facts.pathsToOwned[i].ApplyNext(state, i);
                        }

                        state.NextTurn(commands, false);
                        simulations++;
                        if (state.isGameOver || state.players[player].status == PlayerStatus.Eliminated || facts.pathsToOwned[player].len == 0 && state.players[player].arriveTime == 0)
                        {
                            if (state.players[player].status != PlayerStatus.Eliminated)
                            {
                                if (!opponentCapturedFound && state.players[player].opponentTerritoryCaptured > 0)
                                {
                                    opponentCapturedFound = true;
                                }

                                if (!opponentCapturedFound || state.players[player].opponentTerritoryCaptured > 0)
                                {
                                    var score = estimator.Estimate(state, facts, player, reliablePathBuilder.startLen);
                                    if (score > bestScore || score > bestScore - 1e-6 && reliablePathBuilder.len < bestLen)
                                    {
                                        bestScore       = score;
                                        bestDir         = dir;
                                        bestLen         = reliablePathBuilder.len;
                                        bestPath        = facts.pathsToOwned[player].Print();
                                        bestPathCounter = pathCounter;
                                        Logger.Debug($"Score: {bestScore}; Path: {bestPath}");
                                        if (Logger.IsEnabled(Logger.Level.Debug))
                                        {
                                            for (var i = 0; i < state.players.Length; i++)
                                            {
                                                Logger.Debug($"{i}: {facts.pathsToOwned[i].Print()} {state.players[i].status}");
                                            }
                                        }
                                    }
                                }
                            }

                            break;
                        }
                    }

                    backup.Restore(state);
                }
            }

            if (bestDir == null)
            {
                if (TryGotoStart(state, player, allowedDirectionsMask, out var gotoStart))
                {
                    return(gotoStart);
                }

                facts.pathsToOwned[player].BuildPath(state, distanceMap, player, distanceMap.nearestOwned[player]);
                if (facts.pathsToOwned[player].len > 0)
                {
                    var returnAction = facts.pathsToOwned[player].CurrentAction();
                    if ((allowedDirectionsMask & (1 << (int)returnAction)) != 0 || allowedDirectionsMask == 0)
                    {
                        return new RequestOutput {
                                   Command = returnAction, Debug = $"No path found. Returning back to territory. Paths: {pathCounter}. ValidPaths: {validPathCounter}. Simulations: {simulations}. AllowedDirections: {AllowedDirectionsFinder.DescribeAllowedDirectionsMask(allowedDirectionsMask)} (depth {allowedDirectionsFinder.minimax.bestDepth})"
                        }
                    }
                    ;
                }

                Direction?validDir = null;
                if (state.players[player].dir == null)
                {
                    for (var d = 0; d < 4; d++)
                    {
                        if ((allowedDirectionsMask & (1 << d)) != 0)
                        {
                            var next = state.players[player].arrivePos.NextCoord((Direction)d);
                            if (next != ushort.MaxValue)
                            {
                                validDir = (Direction)d;
                                if (state.territory[next] == player)
                                {
                                    return new RequestOutput {
                                               Command = (Direction)d, Debug = $"No path found. Walking around (null). Paths: {pathCounter}. ValidPaths: {validPathCounter}. Simulations: {simulations}. AllowedDirections: {AllowedDirectionsFinder.DescribeAllowedDirectionsMask(allowedDirectionsMask)} (depth {allowedDirectionsFinder.minimax.bestDepth})"
                                    }
                                }
                                ;
                            }
                        }
                    }
                }
                else
                {
                    var sd = random.Next(3);
                    for (var d = 0; d < 4; d++)
                    {
                        var nd = (Direction)(((int)state.players[player].dir.Value + 3 + sd + d) % 4);
                        if (nd == (Direction)(((int)state.players[player].dir.Value + 2) % 4))
                        {
                            continue;
                        }
                        if ((allowedDirectionsMask & (1 << (int)nd)) != 0)
                        {
                            var next = state.players[player].arrivePos.NextCoord(nd);
                            if (next != ushort.MaxValue)
                            {
                                validDir = nd;
                                if (state.territory[next] == player)
                                {
                                    return new RequestOutput {
                                               Command = nd, Debug = $"No path found. Walking around. Paths: {pathCounter}. ValidPaths: {validPathCounter}. Simulations: {simulations}. AllowedDirections: {AllowedDirectionsFinder.DescribeAllowedDirectionsMask(allowedDirectionsMask)} (depth {allowedDirectionsFinder.minimax.bestDepth})"
                                    }
                                }
                                ;
                            }
                        }
                    }
                }

                return(new RequestOutput {
                    Command = validDir ?? throw new InvalidOperationException("validDir is null"), Debug = $"No path found. Walking around (not self). Paths: {pathCounter}. ValidPaths: {validPathCounter}. Simulations: {simulations}. AllowedDirections: {AllowedDirectionsFinder.DescribeAllowedDirectionsMask(allowedDirectionsMask)} (depth {allowedDirectionsFinder.minimax.bestDepth})"
                });