Skip to content

Instantly share code, notes, and snippets.

@Enigo
Created August 26, 2020 21:03
Show Gist options
  • Save Enigo/5f56ef89a3197a82acce6963fad21274 to your computer and use it in GitHub Desktop.
Save Enigo/5f56ef89a3197a82acce6963fad21274 to your computer and use it in GitHub Desktop.
LevelsChecker - click to expand!
namespace GameData
{
    public class LevelsChecker
    {
        private readonly int _sideSize;

        public LevelsChecker(int sideSize)
        {
            _sideSize = sideSize;
        }

        public bool LevelPassable(List<int> occupiedPositions, List<int> counts)
        {
            var graph = new Graph(_sideSize, occupiedPositions, counts);
            var results = graph.PerformSearch();

            if (results.Count <= 0)
            {
                return false;
            }

            Debug.LogFormat($"Total winning combinations {results.Count}");
            foreach (var connections in results)
            {
                Debug.LogFormat($"{string.Join(",", connections)}");
            }
            return true;
        }
    }
	
    public class Graph
    {
        public HashSet<IEnumerable<Connection>> PerformSearch()
        {
            GatherCombinations();
            return ProcessResults();
        }
		
        private HashSet<IEnumerable<Connection>> ProcessResults()
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            var allPossibleCombinations = CartesianProduct(_connectionsMapping.Values).ToList();
            Debug.LogFormat($"all possible combinations {allPossibleCombinations.Count} in {stopwatch.Elapsed}");
			stopwatch.Stop();

            stopwatch.Reset();
            stopwatch.Start();
            var totalCount = _positionToCount.Values.Sum();
            var requiredPathLength = _verticesInGraph - _occupiedPositions.Count;
            var winningCombinations = new List<WinningCombination>(100);
            // this one has incredibly long execution time - exponential!!!!
            for (var i = 0; i < allPossibleCombinations.Count; i++)
            {
                var currentPath = new List<int>(16);
                var counts = new Dictionary<int, int>(_positionToCount);
                var countLeft = totalCount;
                var combinationConnections = new List<Connection>(totalCount);
                
                foreach (var connection in allPossibleCombinations[i])
                {
                    var path = connection.GetPath();
                    if (currentPath.Any(index => path.Contains(index)))
                    {
                        break;
                    }
            
                    var fromCount = counts[connection.GetFrom()];
                    var toCount = counts[connection.GetTo()];
                    if (fromCount <= 0 || toCount <= 0)
                    {
                        continue;
                    }
            
                    currentPath.AddRange(path);
                    counts[connection.GetFrom()] = --fromCount;
                    counts[connection.GetTo()] = --toCount;
                    combinationConnections.Add(connection);
                    countLeft -= 2;
                    if (countLeft <= 0)
                    {
                        break;
                    }
                }

                if (countLeft == 0 && currentPath.Count == requiredPathLength)
                {
                    winningCombinations.Add(new WinningCombination {Index = i, Connections = combinationConnections});
                }
            }
            stopwatch.Stop();
            Debug.LogFormat($"winning combinations {winningCombinations.Count} in {stopwatch.Elapsed}");
            
            return FilterDuplicateResults(winningCombinations, allPossibleCombinations);
        }

        private static HashSet<IEnumerable<Connection>> FilterDuplicateResults(List<WinningCombination> winningCombinations,
            List<IEnumerable<Connection>> allPossibleCombinations)
        {
            // duplicates are possible due to the nature of the CartesianProduct algorithm
            // when the same set of values works in different combinations  
            var uniques = new HashSet<IEnumerable<Connection>>(new SequenceComparer<Connection>());
            foreach (var winningCombination in winningCombinations)
            {
                var connections = allPossibleCombinations[winningCombination.Index];
                var combination = connections.Intersect(winningCombination.Connections);
                uniques.Add(combination);
            }

            return uniques;
        }


        // https://stackoverflow.com/questions/32263241/cartesian-product-in-c-sharp-using-enumeratos
        private static IEnumerable<IEnumerable<Connection>> CartesianProduct(IEnumerable<IEnumerable<Connection>> sequences) {
            IEnumerable<IEnumerable<Connection>> emptyProduct = new[] { Enumerable.Empty<Connection>() };
            return sequences.Aggregate(
                emptyProduct,
                (accumulator, sequence) =>
                    from acc in accumulator
                    from item in sequence
                    select acc.Concat(new [] { item }));
        }
        
        private class WinningCombination
        {
            public int Index { get; set; }
            public List<Connection> Connections { get; set; }
        }
        
        private class SequenceComparer<T> : IEqualityComparer<IEnumerable<T>>
        {
            public bool Equals(IEnumerable<T> seq1, IEnumerable<T> seq2)
            {
                return seq1.SequenceEqual(seq2);
            }

            public int GetHashCode(IEnumerable<T> seq)
            {
                return seq.Aggregate(53, (current, elem) => current * 37 + elem.GetHashCode());
            }
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment