public bool Evaluate(
            SolverState state,
            ISolverQueue queue,
            ISolverPool pool,
            ISolverPool solutionPool,
            SolverNode node)
        {
            if (node.HasChildren)
            {
                throw new InvalidOperationException();
            }


            node.Status = SolverNodeStatus.Evaluting;
            var toEnqueue = new List <SolverNode>();

            var solution = false;

            foreach (var move in node.MoveMap.TruePositions())
            {
                foreach (var dir in VectorInt2.Directions)
                {
                    var p   = move;
                    var pp  = p + dir;
                    var ppp = pp + dir;
                    if (node.CrateMap[pp] &&                                 // crate to push
                        state.StaticMaps.FloorMap[ppp] && !node.CrateMap[ppp] && // into free space?
                        !state.StaticMaps.DeadMap[ppp])                      // Valid Push
                    {
                        EvaluateValidPush(state, pool, solutionPool, node, pp, ppp, p, dir, toEnqueue, ref solution);
                    }
                }
            }

            if (solution)
            {
                node.Status = SolverNodeStatus.SolutionPath;
            }
            else if (node.HasChildren)
            {
                node.Status = SolverNodeStatus.Evaluted;
                state.Statistics.TotalDead += node.CheckDead();    // Children may be evaluated as dead already
            }
            else
            {
                node.Status = SolverNodeStatus.Dead;
                state.Statistics.TotalDead++;
                if (node.Parent != null)
                {
                    state.Statistics.TotalDead += node.Parent.CheckDead();
                }
            }

            queue.Enqueue(toEnqueue);
            pool.Add(toEnqueue);

            return(solution);
        }
Exemple #2
0
 private void GenerateReports(SolverState state, ISolver solver)
 {
     // var r = state.Command.Report;
     // if (r == null) return;
     //
     // var renderer = new MapToReportingRendererText();
     // var finalStats = solver.Statistics;
     // if (finalStats != null)
     // {
     //     r.WriteLine("### Statistics ###");
     //
     //
     //     MapToReporting.Create<SolverStatistics>()
     //                   .AddColumn("Name", x=>x.Name)
     //                   .AddColumn("Nodes", x=>x.TotalNodes)
     //                   .AddColumn("Avg. Speed", x=>x.NodesPerSec)
     //                   .AddColumn("Duration (sec)", x=>x.DurationInSec)
     //                   .AddColumn("Duplicates", x=>x.Duplicates < 0 ? null : (int?)x.Duplicates)
     //                   .AddColumn("Dead", x=>x.TotalDead < 0 ? null : (int?)x.TotalDead)
     //                   .AddColumn("Current Depth", x=>x.DepthCurrent < 0 ? null : (int?)x.DepthCurrent)
     //                   .RenderTo(finalStats, renderer, r);
     // }
     //
     // var repDepth = MapToReporting.Create<SolverHelper.DepthLineItem>()
     //                        .AddColumn("Depth", x => x.Depth)
     //                        .AddColumn("Total", x => x.Total)
     //                        .AddColumn("Growth Rate", x => x.GrowthRate)
     //                        .AddColumn("UnEval", x => x.UnEval)
     //                        .AddColumn("Complete", x => (x.Total - x.UnEval) *100 / x.Total, c=>c.ColumnInfo.AsPercentage());
     //
     // if (state is MultiThreadedSolverState multi)
     // {
     //     r.WriteLine("### Forward Tree ###");
     //     repDepth.RenderTo(SolverHelper.ReportDepth(multi.Root), renderer, r);
     //
     //     r.WriteLine("### Reverse Tree ###");
     //     repDepth.RenderTo(SolverHelper.ReportDepth(multi.RootReverse), renderer, r);
     // }
     // else if (state is SingleThreadedForwardReverseSolver.State sts)
     // {
     //     r.WriteLine("### Forward Tree ###");
     //     repDepth.RenderTo(SolverHelper.ReportDepth(sts.Forward?.Root), renderer, r);
     //
     //     r.WriteLine("### Reverse Tree ###");
     //     repDepth.RenderTo(SolverHelper.ReportDepth(sts.Reverse?.Root), renderer, r);
     // }
     // else if (state is SolverBaseState sb)
     // {
     //     r.WriteLine("### Forward Tree ###");
     //     repDepth.RenderTo(SolverHelper.ReportDepth(sb.Root), renderer, r);
     // }
     // else
     // {
     //     // ?
     // }
 }
        public override bool Evaluate(SolverState state, ISolverQueue queue, ISolverPool myPool,
                                      ISolverPool solutionPool, SolverNode node)
        {
            if (node.HasChildren)
            {
                throw new InvalidOperationException();
            }

            node.Status = SolverNodeStatus.Evaluting;

            var solution  = false;
            var toEnqueue = new List <SolverNode>(); // TODO: Could be reused
            var toPool    = new List <SolverNode>(); // TODO: Could be reused

            foreach (var move in node.MoveMap.TruePositions())
            {
                foreach (var dir in VectorInt2.Directions)
                {
                    var p  = move;
                    var pc = p + dir;
                    var pp = p - dir;
                    if (node.CrateMap[pc] && // crate to push
                        state.StaticMaps.FloorMap[pp] && !node.CrateMap[p] &&
                        !CheckDeadReverse(state, pp))
                    {
                        EvaluateValidPull(state, myPool, solutionPool, node, pc, p, pp, toEnqueue, toPool, ref solution);
                    }
                }
            }

            if (solution)
            {
                node.Status = SolverNodeStatus.SolutionPath;
            }
            else if (node.HasChildren)
            {
                node.Status = SolverNodeStatus.Evaluted;
                state.Statistics.TotalDead += node.CheckDead(); // Children may be evaluated as dead already
            }
            else
            {
                node.Status = SolverNodeStatus.Dead;
                state.Statistics.TotalDead++;
                if (node.Parent != null)
                {
                    state.Statistics.TotalDead += node.Parent.CheckDead();
                }
            }


            queue.Enqueue(toEnqueue);
            myPool.Add(toEnqueue);

            return(solution);
        }
Exemple #4
0
        public static void GetSolutions(SolverState state, bool check)
        {
            var walls = state.Command.Puzzle.ToMap(state.Command.Puzzle.Definition.Wall);


            state.Solutions = new List <Path>();

            if (state.SolutionsNodes != null)
            {
                state.Solutions.AddRange(state.SolutionsNodes.Select(x => ConvertSolutionNodeToPath(x, walls, state.Command.Puzzle)));
            }

            if (state.SolutionsNodesReverse != null)
            {
                foreach (var tuple in state.SolutionsNodesReverse)
                {
                    var rev    = ConvertReverseNodeToPath(tuple.ReverseNode, walls);
                    var fwd    = ConvertForwardNodeToPath(tuple.ForwardNode, walls);
                    var bridge = PathFinder.Find(walls.BitwiseOR(tuple.ForwardNode.CrateMap),
                                                 tuple.ForwardNode.PlayerAfter, tuple.ReverseNode.PlayerAfter);

                    //Console.WriteLine("Forward Leg: {0}", fwd);
                    //Console.WriteLine("Reverse Leg: {0}", rev);
                    //Console.WriteLine("Bridge {0} => {1}", tuple.ForwardNode.ToStringDebugPositions(),
                    //    tuple.ReverseNode.ToStringDebugPositions());

                    var p = new Path();
                    p.AddRange(fwd);
                    p.AddRange(bridge);
                    p.AddRange(rev);
                    state.Solutions.Add(p);
                }
            }

            if (check && state.Solutions.Any())
            {
                int cc = 0;
                foreach (var pathSolution in state.Solutions.ToArray())
                {
                    cc++;
                    if (!CheckSolution(state.Command.Puzzle, pathSolution, out var error))
                    {
                        state.SolutionsInvalid ??= new List <(Path, string)>();
                        state.SolutionsInvalid.Add((pathSolution, error));
                        state.Solutions.Remove(pathSolution);

                        if (state.Command.Report != null)
                        {
                            state.Command.Report.WriteLine($"Solution #{cc++} [{(check ? "Valid" : "INVALID!" + error)}] =>\n{pathSolution}");
                        }
                    }
                }
            }
        }
        public void Update(ISolver caller, SolverState state, SolverStatistics global, string txt)
        {
            var dt = DateTime.Now - last;

            if (dt.TotalSeconds < sampleRateInSec)
            {
                return;
            }
            last = DateTime.Now;

            UpdateInner(caller, state, global, txt);
        }
        private bool CheckValidSolutions(SolverState state, SolverNode posibleSolution)
        {
            var b = state.StaticMaps.WallMap.BitwiseOR(state.StaticMaps.CrateStart);
            var f = state.Command.Puzzle.Player.Position;

            var path = posibleSolution.PathToRoot();

            path.Reverse();
            var start = path[0];
            var t     = start.PlayerAfter;
            var first = PathFinder.Find(b, f, t);

            return(first != null);
        }
        protected override string Render(ISolver caller, SolverState state, SolverStatistics global, string txt)
        {
            if (global == null)
            {
                return(null);
            }

            var totalMemory = System.GC.GetTotalMemory(false);

            var delta = (prev != null) ? global.TotalNodes - prev.TotalNodes : global.TotalNodes;

            var sb = new FluentString()
                     .Append(txt)
                     .Sep()
                     .Append($"delta:{delta:#,##0}")
                     .Sep()
                     .Append($"mem({Humanise.SizeSuffix((ulong) totalMemory)} used")
                     .Block(b =>
            {
                if (DevHelper.TryGetTotalMemory(out var avail))
                {
                    b.Sep();
                    b.Append($"{Humanise.SizeSuffix(avail)} avail");
                }
            })
                     .Append(")");


            var telText = new FluentString(",")
                          .Append(global.DurationInSec.ToString()).Sep()
                          .Append(global.TotalNodes.ToString()).Sep()
                          .Append(global.NodesPerSec.ToString()).Sep()
                          .Append(delta.ToString()).Sep()
                          .Append(totalMemory.ToString()).Sep()
            ;

            tele.WriteLine(telText);

            prev = new SolverStatistics(global);



            return(sb);
        }
Exemple #8
0
        public static string GenerateSummary(SolverState state)
        {
            if (state == null)
            {
                throw new ArgumentNullException("state");
            }

            var sb         = new StringBuilder();
            var nodePerSec = (double)state.Statistics.TotalNodes / state.Statistics.DurationInSec;

            sb.Append(state.Exit.ToString().PadRight(20));

            if (state.Exception != null)
            {
                var ex = state.Exception is AggregateException agg
                    ? agg.InnerExceptions.First()
                    : state.Exception;

                if (ex is NullReferenceException)
                {
                    sb.Append("[NULL] " + StringUtil.ToLines(ex.StackTrace).First().TrimStart('\t').Trim());
                }
                else
                {
                    sb.Append(StringUtil.Truncate(StringUtil.StripLineFeeds($"[{ex.GetType().Name}] {ex.Message}"), 180));
                }
                return(sb.ToString());
            }

            sb.Append($" {state.Statistics.TotalNodes,12:#,##0} nodes at {nodePerSec,6:#,##0}/s in {state.Statistics.Elapsed.Humanize()}.");
            if (state.HasSolution)
            {
                var d = state.SolutionsNodes != null ? state.SolutionsNodes.Count : 0;
                var r = state.SolutionsNodesReverse != null ? state.SolutionsNodesReverse.Count : 0;

                sb.AppendFormat(" {0} solutions.", d + r);
            }

            if (state.SolutionsInvalid != null && state.SolutionsInvalid.Count > 0)
            {
                sb.Append(" !INVALID SOLUTIONS!");
            }
            return(sb.ToString());
        }
        private void GenerateReports(SolverState state, ISolver solver)
        {
            var r = state.Command.Report;

            if (r == null)
            {
                return;
            }

            var renderer   = new MapToReportingRendererText();
            var finalStats = solver.Statistics;

            if (finalStats != null)
            {
                r.WriteLine("### Statistics ###");


                MapToReporting.Create <SolverStatistics>()
                .AddColumn("Name", x => x.Name)
                .AddColumn("Nodes", x => x.TotalNodes)
                .AddColumn("Avg. Speed", x => x.NodesPerSec)
                .AddColumn("Duration (sec)", x => x.DurationInSec)
                .AddColumn("Duplicates", x => x.Duplicates < 0 ? null : (int?)x.Duplicates)
                .AddColumn("Dead", x => x.TotalDead < 0 ? null : (int?)x.TotalDead)
                .AddColumn("Current Depth", x => x.DepthCurrent < 0 ? null : (int?)x.DepthCurrent)
                .RenderTo(finalStats, renderer, Report);
            }

            var repDepth = MapToReporting.Create <SolverHelper.DepthLineItem>()
                           .AddColumn("Depth", x => x.Depth)
                           .AddColumn("Total", x => x.Total)
                           .AddColumn("UnEval", x => x.UnEval)
                           .AddColumn("Complete", x => (x.Total - x.UnEval) * 100 / x.Total, c => c.ColumnInfo.AsPercentage());

            if (state is MultiThreadedSolverState multi)
            {
                r.WriteLine("### Forward Tree ###");
                repDepth.RenderTo(SolverHelper.ReportDepth(multi.Root), renderer, r);

                r.WriteLine("### Reverse Tree ###");
                repDepth.RenderTo(SolverHelper.ReportDepth(multi.RootReverse), renderer, r);
            }
        }
        private void NewSolutionChain(SolverState state, out bool solution, SolverNode newKid, SolverNode match)
        {
            solution = true;
            state.SolutionsNodesReverse ??= new List <SolutionChain>();
            var pair = new SolutionChain
            {
                ForwardNode = newKid,
                ReverseNode = match,
                FoundUsing  = this
            };

            state.SolutionsNodesReverse.Add(pair);

            state.Command.Debug.Raise(this, SolverDebug.Solution, pair);

            foreach (var n in newKid.PathToRoot().Union(match.PathToRoot()))
            {
                n.Status = SolverNodeStatus.SolutionPath;
            }
            newKid.Status = SolverNodeStatus.Solution;
            match.Status  = SolverNodeStatus.Solution;
        }
 public IEnumerable <(string name, string text)> GetTypeDescriptorProps(SolverState state) =>
 public virtual IEnumerable <(string name, string text)> GetTypeDescriptorProps(SolverState state) => ImmutableArray <(string name, string text)> .Empty;
 public IEnumerable <(string name, string text)> GetTypeDescriptorProps(SolverState state) => throw new NotSupportedException();
        public ExitConditions.Conditions Solve(SolverState state)
        {
            var full     = (MultiThreadedSolverState)state;
            var allTasks = full.Workers.Select(x => (Task)x.Task).ToArray();
            var cancel   = state.Command.CancellationToken;

            // Start and wait
            full.IsRunning = true;
            foreach (var worker in full.Workers)
            {
                worker.Task.Start();
            }

            Task statisticsTick = null;

            if (state.Command.AggProgress != null)
            {
                // Setup global/aggregate statistics and updates
                statisticsTick = Task.Run(() =>
                {
                    while (full.IsRunning)
                    {
                        Thread.Sleep(1000);
                        state.Statistics.TotalNodes = current.PoolForward.Statistics.TotalNodes
                                                      + current.PoolReverse.Statistics.TotalNodes;

                        var txt = new FluentString()
                                  .Append($"==> {state.Statistics.ToString(false, true)}")
                                  .Append($" Fwd({current.PoolForward.Statistics.TotalNodes:#,##0}|{current.QueueForward.Statistics.TotalNodes:#,##0})")
                                  .Append($" Rev({current.PoolReverse.Statistics.TotalNodes:#,##0}|{current.QueueReverse.Statistics.TotalNodes:#,##0})");
                        state.Command.AggProgress.Update(this, state, state.Statistics, txt);
                    }
                    if (state.Command.AggProgress is IDisposable dp)
                    {
                        dp.Dispose();
                    }
                });
            }

            if (!Task.WaitAll(allTasks, (int)state.Command.ExitConditions.Duration.TotalMilliseconds, cancel))
            {
                // Close down the workers as gracefully as possible
                state.Command.ExitConditions.ExitRequested = true;

                // Allow them to process the ExitRequested
                Thread.Sleep((int)TimeSpan.FromSeconds(1).TotalMilliseconds);

                // Close down any outlyers
                foreach (var task in allTasks)
                {
                    if (task.Status == TaskStatus.Running)
                    {
                        if (!task.Wait((int)TimeSpan.FromSeconds(1).TotalMilliseconds))
                        {
                            task.Dispose();
                        }
                    }
                }

                state.Exit = ExitConditions.Conditions.TimeOut;
            }
            full.IsRunning = false;
            statisticsTick?.Wait();
            state.Statistics.Completed = DateTime.Now;

            foreach (var stat in current.StatsInner)
            {
                stat.Completed = state.Statistics.Completed;
            }

            // Get solutions & Exit Conditions & Errors
            var errors = full.Workers.Select(x => x.WorkerState.Exception).Where(x => x != null).ToList();

            if (errors.Any())
            {
                throw new AggregateException(errors);
            }
            foreach (var worker in full.Workers)
            {
                worker.WorkerState.Statistics.Completed = state.Statistics.Completed;

                // Bubble up exit to owner
                state.Command.Report?.WriteLine($"WorkerExit: {worker.Name} -> {worker.WorkerState.Exit}");

                if (state.Exit == ExitConditions.Conditions.InProgress &&
                    (worker.WorkerState.Exit != ExitConditions.Conditions.InProgress && worker.WorkerState.Exit != ExitConditions.Conditions.Aborted))
                {
                    state.Exit = worker.WorkerState.Exit;
                }

                if (worker.WorkerState.HasSolution)
                {
                    if (worker.WorkerState.SolutionsNodes != null)
                    {
                        full.SolutionsNodes ??= new List <SolverNode>();
                        full.SolutionsNodes.AddRange(worker.WorkerState.SolutionsNodes);
                        state.Exit = ExitConditions.Conditions.Solution;
                    }

                    if (worker.WorkerState.SolutionsNodesReverse != null)
                    {
                        full.SolutionsNodesReverse ??= new List <SolutionChain>();
                        full.SolutionsNodesReverse.AddRange(worker.WorkerState.SolutionsNodesReverse);
                        state.Exit = ExitConditions.Conditions.Solution;
                    }
                }
            }

            // Update stats
            state.Statistics.TotalNodes = current.PoolForward.Statistics.TotalNodes
                                          + current.PoolReverse.Statistics.TotalNodes;

            SolverHelper.GetSolutions(state, true);



            if (state.Exit == ExitConditions.Conditions.Continue)
            {
                state.Exit = full.Workers.Select(x => x.WorkerState.Exit)
                             .GroupBy(x => x)
                             .OrderBy(x => x.Count())
                             .First().Key;
            }

            return(state.Exit);
        }
Exemple #15
0
        private int StoreAttempt(ISolver solver, LibraryPuzzle dto, SolverState state, string desc)
        {
            var best = state.Solutions?.OrderBy(x => x.Count).FirstOrDefault();


            var sol = new SolutionDTO
            {
                IsAutomated        = true,
                PuzzleIdent        = dto.Ident.ToString(),
                Path               = best?.ToString(),
                Created            = DateTime.Now,
                Modified           = DateTime.Now,
                MachineName        = Environment.MachineName,
                MachineCPU         = DevHelper.DescribeCPU(),
                SolverType         = solver.GetType().Name,
                SolverVersionMajor = solver.VersionMajor,
                SolverVersionMinor = solver.VersionMinor,
                SolverDescription  = solver.VersionDescription,
                TotalNodes         = solver.Statistics.First().TotalNodes,
                TotalSecs          = solver.Statistics.First().DurationInSec,
                Description        = desc
            };

            var exists = Repository.GetPuzzleSolutions(dto.Ident);

            if (exists != null && exists.Any())
            {
                var onePerMachine = exists.FirstOrDefault(x => x.MachineName == sol.MachineName && x.SolverType == sol.SolverType);
                if (onePerMachine != null)
                {
                    if (sol.HasSolution)
                    {
                        if (!onePerMachine.HasSolution)
                        {
                            sol.SolutionId = onePerMachine.SolutionId; // replace
                            Repository.Store(sol);
                            return(sol.SolutionId);
                        }
                        else if (sol.TotalNodes < onePerMachine.TotalSecs)
                        {
                            sol.SolutionId = onePerMachine.SolutionId; // replace
                            Repository.Store(sol);
                            return(sol.SolutionId);
                        }
                        else
                        {
                            // drop
                        }
                    }
                    else
                    {
                        if (!onePerMachine.HasSolution && sol.TotalNodes > onePerMachine.TotalNodes)
                        {
                            sol.SolutionId = onePerMachine.SolutionId; // replace
                            Repository.Store(sol);
                            return(sol.SolutionId);
                        }
                    }
                }
                else
                {
                    Repository.Store(sol);
                    return(sol.SolutionId);
                }
            }
            else
            {
                Repository.Store(sol);
                return(sol.SolutionId);
            }

            return(-1);
        }
Exemple #16
0
 public abstract bool Evaluate(SolverState state, ISolverQueue queue, ISolverPool pool, ISolverPool solutionPool, SolverNode node);
 protected abstract void UpdateInner(ISolver caller, SolverState state, SolverStatistics global, string txt);
        private bool EvaluateValidPush(
            SolverState state,
            ISolverPool pool,
            ISolverPool reversePool,
            SolverNode node,
            VectorInt2 pp,
            VectorInt2 ppp,
            VectorInt2 p,
            VectorInt2 push,
            List <SolverNode> toEnqueue,
            ref bool solution)
        {
            state.Statistics.TotalNodes++;


            var newKid = nodeFactory.CreateFromPush(node, node.CrateMap, state.StaticMaps.WallMap, p, pp, ppp, push);

            // Cycle Check: Does this node exist already?
            var dup = pool.FindMatch(newKid);

            if (dup != null)
            {
                if (object.ReferenceEquals(dup, newKid))
                {
                    throw new InvalidDataException();
                }
                if (dup.SolverNodeId == newKid.SolverNodeId)
                {
                    throw new InvalidDataException();
                }

                // Duplicate
                newKid.Status = SolverNodeStatus.Duplicate;
                state.Statistics.Duplicates++;

                if (state.Command.DuplicateMode == DuplicateMode.AddAsChild)
                {
                    node.Add(newKid);
                    newKid.Duplicate = dup;
                }
                else if (state.Command.DuplicateMode == DuplicateMode.ReuseInPool)
                {
                    nodeFactory.ReturnInstance(newKid); // Add to pool for later re-use?
                }
                else // DuplicateMode.Discard
                {
                }
            }
            else
            {
                node.Add(newKid);

                // If there is a reverse solver, checks its pool for a match, hence a Forward <-> Reverse chain, hence a solution
                var match = reversePool?.FindMatch(newKid);
                if (match != null)
                {
                    // Solution!
                    NewSolutionChain(state, out solution, newKid, match);

                    if (state.Command.ExitConditions.StopOnSolution)
                    {
                        return(true);
                    }
                }
                else
                {
                    if (DeadMapAnalysis.DynamicCheck(state.StaticMaps, node))
                    {
                        newKid.Status = SolverNodeStatus.Dead;
                        state.Statistics.TotalDead++;
                    }
                    else
                    {
                        toEnqueue.Add(newKid);

                        if (newKid.IsSolutionForward(state.StaticMaps))
                        {
                            // Solution
                            solution = true;
                            state.SolutionsNodes.Add(newKid);
                            state.Command.Debug.Raise(this, SolverDebug.Solution, newKid);
                            foreach (var n in newKid.PathToRoot())
                            {
                                n.Status = SolverNodeStatus.SolutionPath;
                            }
                            newKid.Status = SolverNodeStatus.Solution;

                            if (state.Command.ExitConditions.StopOnSolution)
                            {
                                return(true);
                            }
                        }
                    }
                }
            }

            return(false);
        }
        private bool EvaluateValidPull(
            SolverState state,
            ISolverPool myPool,
            ISolverPool solutionPool,
            SolverNode node,
            VectorInt2 pc,
            VectorInt2 p,
            VectorInt2 pp,
            List <SolverNode> toEnqueue,
            ref bool solution)
        {
            state.Statistics.TotalNodes++;


            var newKid = nodeFactory.CreateFromPull(node, node.CrateMap, state.StaticMaps.WallMap, pc, p, pp);


            // Cycle Check: Does this node exist already?
            var dup = myPool.FindMatch(newKid);

            if (dup != null)
            {
                if (object.ReferenceEquals(dup, newKid))
                {
                    throw new InvalidDataException();
                }
                if (dup.SolverNodeId == newKid.SolverNodeId)
                {
                    throw new InvalidDataException();
                }

                // Duplicate
                newKid.Status = SolverNodeStatus.Duplicate;
                state.Statistics.Duplicates++;

                if (state.Command.DuplicateMode == DuplicateMode.AddAsChild)
                {
                    node.Add(newKid);
                    newKid.Duplicate = dup;
                }
                else if (state.Command.DuplicateMode == DuplicateMode.ReuseInPool)
                {
                    nodeFactory.ReturnInstance(newKid); // Add to pool for later re-use?
                }
                else // DuplicateMode.Discard
                {
                }
            }
            else
            {
                var match = solutionPool?.FindMatch(newKid);
                if (match != null)
                {
                    // Add to tree / itterator
                    node.Add(newKid);

                    // Solution
                    state.SolutionsNodesReverse ??= new List <SolutionChain>();
                    var pair = new SolutionChain
                    {
                        ForwardNode = match,
                        ReverseNode = newKid,
                        FoundUsing  = this
                    };
                    state.SolutionsNodesReverse.Add(pair);
                    solution = true;
                    state.Command.Debug.Raise(this, SolverDebug.Solution, pair);

                    foreach (var n in newKid.PathToRoot().Union(match.PathToRoot()))
                    {
                        n.Status = SolverNodeStatus.SolutionPath;
                    }
                    newKid.Status = SolverNodeStatus.Solution;
                    match.Status  = SolverNodeStatus.Solution;
                    if (state.Command.ExitConditions.StopOnSolution)
                    {
                        return(true);
                    }
                }
                else
                {
                    // Add to tree / iterator
                    node.Add(newKid);  // Thread-safe: As all kids get created in this method (forward / reverse)

                    if (DeadMapAnalysis.DynamicCheck(state.StaticMaps, node))
                    {
                        newKid.Status = SolverNodeStatus.Dead;
                    }
                    else
                    {
                        toEnqueue.Add(newKid);
                        if (newKid.IsSolutionReverse(state.StaticMaps))
                        {
                            // Possible Solution: Did we start in a valid position
                            if (CheckValidSolutions(state, newKid))
                            {
                                state.SolutionsNodes.Add(newKid);
                                state.Command.Debug.Raise(this, SolverDebug.Solution, newKid);
                                solution = true;

                                foreach (var n in newKid.PathToRoot())
                                {
                                    n.Status = SolverNodeStatus.SolutionPath;
                                }
                                newKid.Status = SolverNodeStatus.Solution;
                            }
                            else
                            {
                                // We started in the wrong place; ignore and continue
                            }
                        }
                    }
                }
            }

            return(false);
        }
        private bool CheckDeadReverse(SolverState state, VectorInt2 ppp)
        {
            return(false);

            return(state.StaticMaps.DeadMap[ppp]);
        }
 protected abstract string Render(ISolver caller, SolverState state, SolverStatistics global, string txt);
        private bool EvaluateValidPull(
            SolverState state,
            ISolverPool pool,
            ISolverPool solutionPool,
            SolverNode node,
            VectorInt2 pc,
            VectorInt2 p,
            VectorInt2 pp,
            List <SolverNode> toEnqueue,
            List <SolverNode> toPool,
            ref bool solution)
        {
            state.Statistics.TotalNodes++;


            var newKid = nodeFactory.CreateFromPull(node, node.CrateMap, state.StaticMaps.WallMap, pc, p, pp);

            if (state.Command.Inspector != null && state.Command.Inspector(newKid))
            {
                state.Command.Report?.WriteLine(newKid.ToString());
            }

            // Cycle Check: Does this node exist already?
            var dup = pool.FindMatch(newKid);

            if (SafeMode && dup == null)
            {
                dup = ConfirmDupLookup(pool, node, toEnqueue, newKid); // Fix or Throw
            }
            if (dup != null)
            {
                if (object.ReferenceEquals(dup, newKid))
                {
                    throw new InvalidDataException();
                }
                if (dup.SolverNodeId == newKid.SolverNodeId)
                {
                    throw new InvalidDataException();
                }

                // Duplicate
                newKid.Status = SolverNodeStatus.Duplicate;
                state.Statistics.Duplicates++;

                if (state.Command.DuplicateMode == DuplicateMode.AddAsChild)
                {
                    node.Add(newKid);
                    if (newKid is ISolverNodeDuplicateLink dupLink)
                    {
                        dupLink.Duplicate = dup;
                    }
                }
                else if (state.Command.DuplicateMode == DuplicateMode.ReuseInPool)
                {
                    nodeFactory.ReturnInstance(newKid); // Add to pool for later re-use?
                }
                else // DuplicateMode.Discard
                {
                }
            }
            else
            {
                // These two should always be the same
                node.Add(newKid); toPool.Add(newKid);

                // If there is a reverse solver, checks its pool for a match, hence a Forward <-> Reverse chain, hence a solution
                var match = solutionPool?.FindMatch(newKid);
                if (match != null)
                {
                    // Solution
                    state.SolutionsNodesReverse ??= new List <SolutionChain>();
                    var pair = new SolutionChain
                    {
                        ForwardNode = match,
                        ReverseNode = newKid,
                        FoundUsing  = this
                    };
                    state.SolutionsNodesReverse.Add(pair);
                    solution = true;
                    state.Command.Debug.Raise(this, SolverDebug.Solution, pair);

                    foreach (var n in newKid.PathToRoot().Union(match.PathToRoot()))
                    {
                        n.Status = SolverNodeStatus.SolutionPath;
                    }
                    newKid.Status = SolverNodeStatus.Solution;
                    match.Status  = SolverNodeStatus.Solution;
                    if (state.Command.ExitConditions.StopOnSolution)
                    {
                        return(true);
                    }
                }
                else
                {
                    if (DeadMapAnalysis.DynamicCheck(state.StaticMaps, node))
                    {
                        newKid.Status = SolverNodeStatus.Dead;
                    }
                    else
                    {
                        toEnqueue.Add(newKid);
                        if (newKid.IsSolutionReverse(state.StaticMaps))
                        {
                            // Possible Solution: Did we start in a valid position
                            if (CheckValidSolutions(state, newKid))
                            {
                                state.SolutionsNodes.Add(newKid);
                                state.Command.Debug.Raise(this, SolverDebug.Solution, newKid);
                                solution = true;

                                foreach (var n in newKid.PathToRoot())
                                {
                                    n.Status = SolverNodeStatus.SolutionPath;
                                }
                                newKid.Status = SolverNodeStatus.Solution;
                            }
                            else
                            {
                                // We started in the wrong place; ignore and continue
                            }
                        }
                    }
                }
            }

            return(false);
        }