public GunGroup(ProjectileGun[] guns, double caliber, double demandPerGun)
 {
     this.Guns = guns;
     this.Caliber = caliber;
     this.DemandPerGun = demandPerGun;
     this.DemandTotal = demandPerGun * guns.Length;
 }
        private static GunGroup[][] ConvertSets(int[][][] combos, ProjectileGun[] guns, Tuple<double, double>[][] caliberRanges, Func<Tuple<double, double>, double> caliberFunc)
        {
            GunGroup[][] retVal = new GunGroup[combos.Length][];

            for (int i = 0; i < combos.Length; i++)
            {
                GunGroup[] set = new GunGroup[combos[i].Length];

                for (int j = 0; j < combos[i].Length; j++)
                {
                    double caliber = caliberFunc(caliberRanges[i][j]);

                    ProjectileGun[] gunSet = combos[i][j].
                        Select(o => guns[o]).
                        ToArray();

                    double demand = GetAmmoVolume(caliber);

                    set[j] = new GunGroup(gunSet, caliber, demand);
                }

                retVal[i] = set;
            }

            return retVal;
        }
        private static GunGroup[][] GetAllSetCombos(int[][] majorSets, ProjectileGun[] guns)
        {
            // singles
            // unique sets + remainder singles
            int[][][] fullSets = UtilityCore.AllCombosEnumerator(majorSets).ToArray();

            Tuple<double, double>[][] caliberRanges = fullSets.
                Select(o => o.Select(p => GetCaliberRange(guns, p)).ToArray()).
                ToArray();

            if (caliberRanges.SelectMany(o => o).Any(o => o.Item1 > o.Item2))
            {
                throw new ApplicationException("Invalid caliber range.  majorSets was probably built wrong");
            }

            // Turn each full set into a gungroup[]
            List<GunGroup[]> retVal = new List<GunGroup[]>();

            retVal.AddRange(ConvertSets(fullSets, guns, caliberRanges, o => o.Item1));     // min
            retVal.AddRange(ConvertSets(fullSets, guns, caliberRanges, o => o.Item2));     // max
            retVal.AddRange(ConvertSets(fullSets, guns, caliberRanges, o => (o.Item1 + o.Item2) / 2));     // avg

            Random rand = StaticRandom.GetRandomForThread();
            for (int cntr = 0; cntr < 10; cntr++)
            {
                retVal.AddRange(ConvertSets(fullSets, guns, caliberRanges, o => rand.NextDouble(o.Item1, o.Item2)));     // rand
            }

            return retVal.ToArray();
        }
        private static Tuple<double, double> GetCaliberRange(ProjectileGun[] guns, int[] indices)
        {
            return Tuple.Create(
                indices.Max(o => guns[o].CaliberRange.Item1),       // Item1 is as small as the gun can go.  So max(item1) will find the gun that is the bottleneck (the largest of the small range)
                indices.Min(o => guns[o].CaliberRange.Item2));

            //return Tuple.Create(
            //    indices.Min(o => guns[o].Caliber),        // can't just use caliber.  Some guns may have a standard caliber that is outside the range of other guns in the set
            //    indices.Max(o => guns[o].Caliber));
        }
        private static int[][] GetMajorSets(ProjectileGun[] guns)
        {
            if (guns.Length == 1)
            {
                return new[] { new[] { 0 } };
            }

            // Get all the min/max boundries of the guns
            IEnumerable<double> boundries = guns.
                SelectMany(o => new[] { o.CaliberRange.Item1, o.CaliberRange.Item2 }).
                Distinct().
                OrderBy(o => o);

            // Go through each boundry, and see which guns straddle it
            List<int[]> gunsWithBoundry = new List<int[]>();

            foreach (double boundry in boundries)
            {
                int[] matches = Enumerable.Range(0, guns.Length).
                    Where(o => guns[o].CaliberRange.Item1 <= boundry && guns[o].CaliberRange.Item2 >= boundry).
                    OrderBy(o => o).
                    ToArray();

                gunsWithBoundry.Add(matches);
            }

            // Dedupe, remove subsets
            GetMajorSets_Consolidate(gunsWithBoundry);

            return gunsWithBoundry.ToArray();
        }